feat: optimize pagination

This commit is contained in:
Zhang Minghan 2024-03-09 11:57:34 +08:00
parent c993fe34e2
commit 98690e093a
13 changed files with 134 additions and 90 deletions

View File

@ -8,7 +8,7 @@
},
"package": {
"productName": "chatnio",
"version": "3.10.3"
"version": "3.10.4"
},
"tauri": {
"allowlist": {

View File

@ -337,16 +337,15 @@
margin: 0;
background: hsl(var(--background));
transition: 0.2s ease-in-out;
transition-property: width, background, box-shadow, border-right, opacity;
transition-property: width, background, box-shadow;
border-right: 0;
pointer-events: none;
opacity: 0;
overflow-x: hidden;
&.open {
width: 260px;
border-right: 1px solid hsl(var(--border));
pointer-events: auto;
opacity: 1;
}
&.hidden {
@ -362,10 +361,6 @@
padding: 4px;
}
&.open .conversation-list {
opacity: 1;
}
.sidebar-menu {
height: max-content;
width: 100%;
@ -409,7 +404,6 @@
display: flex;
flex-direction: column;
gap: 6px;
opacity: 0;
width: 100%;
height: 100%;
padding: 6px 0;
@ -603,6 +597,7 @@
button {
margin: 0.5rem 0;
white-space: nowrap;
}
.space-footer {

View File

@ -131,7 +131,7 @@ function SelectGroupMobile(props: SelectGroupProps) {
props.onChange?.(value);
}}
>
<SelectTrigger className="select-group mobile">
<SelectTrigger className="select-group mobile whitespace-nowrap flex-nowrap">
<SelectValue placeholder={props.current?.value || ""} />
</SelectTrigger>
<SelectContent

View File

@ -19,19 +19,14 @@ import { useTranslation } from "react-i18next";
import { useState } from "react";
import { InvitationForm, InvitationResponse } from "@/admin/types.ts";
import { Button } from "@/components/ui/button.tsx";
import {
ChevronLeft,
ChevronRight,
Download,
Loader2,
RotateCw,
} from "lucide-react";
import { Download, Loader2, RotateCw } from "lucide-react";
import { useEffectAsync } from "@/utils/hook.ts";
import { generateInvitation, getInvitationList } from "@/admin/api/chart.ts";
import { Input } from "@/components/ui/input.tsx";
import { useToast } from "@/components/ui/use-toast.ts";
import { Textarea } from "@/components/ui/textarea.tsx";
import { saveAsFile } from "@/utils/dom.ts";
import { PaginationAction } from "@/components/ui/pagination.tsx";
function GenerateDialog() {
const { t } = useTranslation();
@ -186,27 +181,12 @@ function InvitationTable() {
))}
</TableBody>
</Table>
<div className={`pagination`}>
<Button
variant={`default`}
size={`icon`}
onClick={() => setPage(page - 1)}
disabled={page === 0}
>
<ChevronLeft className={`h-4 w-4`} />
</Button>
<Button variant={`ghost`} size={`icon`}>
{page + 1}
</Button>
<Button
variant={`default`}
size={`icon`}
onClick={() => setPage(page + 1)}
disabled={page + 1 === data.total}
>
<ChevronRight className={`h-4 w-4`} />
</Button>
</div>
<PaginationAction
current={page}
total={data.total}
onPageChange={setPage}
offset
/>
</>
) : (
<div className={`empty`}>

View File

@ -18,25 +18,28 @@ import { useLocation } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { mobile } from "@/utils/device.ts";
import { cn } from "@/components/ui/lib/utils.ts";
import { Button } from "@/components/ui/button.tsx";
type MenuItemProps = {
title: string;
icon: React.ReactNode;
path: string;
exit?: boolean;
};
function MenuItem({ title, icon, path }: MenuItemProps) {
function MenuItem({ title, icon, path, exit }: MenuItemProps) {
const location = useLocation();
const dispatch = useDispatch();
const active = useMemo(
() =>
location.pathname === `/admin${path}` ||
location.pathname + "/" === `/admin${path}`,
!exit &&
(location.pathname === `/admin${path}` ||
location.pathname + "/" === `/admin${path}`),
[location.pathname, path],
);
const redirect = async () => {
if (exit) return await router.navigate("/");
if (mobile) dispatch(closeMenu());
await router.navigate(`/admin${path}`);
};
@ -87,16 +90,7 @@ function MenuBar() {
icon={<FileClock />}
path={"/logger"}
/>
<div className={`grow mt-6`} />
<Button
variant={`outline`}
size={`icon`}
className={`ml-3`}
onClick={() => router.navigate("/")}
>
<LogOut className={`h-4 w-4`} />
</Button>
<MenuItem title={t("admin.exit")} icon={<LogOut />} path={""} exit />
</div>
);
}

View File

@ -38,8 +38,6 @@ import {
CalendarCheck2,
CalendarClock,
CalendarOff,
ChevronLeft,
ChevronRight,
CloudCog,
CloudFog,
KeyRound,
@ -59,6 +57,7 @@ import { getNumber, parseNumber } from "@/utils/base.ts";
import { useDeeptrain } from "@/conf/env.ts";
import { useSelector } from "react-redux";
import { selectUsername } from "@/store/auth.ts";
import { PaginationAction } from "@/components/ui/pagination.tsx";
type OperationMenuProps = {
user: UserData;
@ -435,27 +434,12 @@ function UserTable() {
))}
</TableBody>
</Table>
<div className={`pagination`}>
<Button
variant={`default`}
size={`icon`}
onClick={() => setPage(page - 1)}
disabled={page === 0}
>
<ChevronLeft className={`h-4 w-4`} />
</Button>
<Button variant={`ghost`} size={`icon`}>
{page + 1}
</Button>
<Button
variant={`default`}
size={`icon`}
onClick={() => setPage(page + 1)}
disabled={page + 1 === data.total}
>
<ChevronRight className={`h-4 w-4`} />
</Button>
</div>
<PaginationAction
current={page}
total={data.total}
onPageChange={setPage}
offset
/>
</>
) : loading ? (
<div className={`flex flex-col mb-4 mt-12 items-center`}>

View File

@ -2,7 +2,7 @@ import * as React from "react";
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "@/components/ui/lib/utils";
import { ButtonProps, buttonVariants } from "src/components/ui/button";
import { ButtonProps, buttonVariants } from "@/components/ui/button";
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
@ -30,7 +30,11 @@ const PaginationItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} />
<li
ref={ref}
className={cn("cursor-pointer select-none", className)}
{...props}
/>
));
PaginationItem.displayName = "PaginationItem";
@ -65,12 +69,11 @@ const PaginationPrevious = ({
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
size="icon"
className={cn("gap-1", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span>Previous</span>
</PaginationLink>
);
PaginationPrevious.displayName = "PaginationPrevious";
@ -81,11 +84,10 @@ const PaginationNext = ({
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
size="icon"
className={cn("gap-1", className)}
{...props}
>
<span>Next</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
);
@ -101,11 +103,94 @@ const PaginationEllipsis = ({
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
);
PaginationEllipsis.displayName = "PaginationEllipsis";
type PaginationActionProps = React.ComponentProps<"div"> & {
current: number;
total: number;
offset?: boolean;
onPageChange: (page: number) => void;
};
const PaginationAction = ({
current,
total,
offset = false,
className,
onPageChange,
children,
...props
}: PaginationActionProps) => {
const real = current + (offset ? 1 : 0);
const diff = total - real;
const hasPrev = current > 0;
const hasNext = diff > 1;
const hasStepPrev = current > 1 && !hasNext;
const hasStepNext = diff > 0 && !hasPrev;
const showRightEllipsis = diff > 2;
const showLeftEllipsis = real > 2 && !showRightEllipsis;
return (
<Pagination className={cn("py-4", className)} {...props}>
<PaginationContent>
<PaginationItem onClick={() => hasPrev && onPageChange(current - 1)}>
<PaginationPrevious />
</PaginationItem>
{showLeftEllipsis && (
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
)}
{hasStepPrev && (
<PaginationItem onClick={() => onPageChange(current - 2)}>
<PaginationLink>{real - 2}</PaginationLink>
</PaginationItem>
)}
{hasPrev && (
<PaginationItem onClick={() => onPageChange(current - 1)}>
<PaginationLink>{real - 1}</PaginationLink>
</PaginationItem>
)}
<PaginationItem>
<PaginationLink isActive>{real}</PaginationLink>
</PaginationItem>
{hasNext && (
<PaginationItem onClick={() => onPageChange(current + 1)}>
<PaginationLink>{real + 1}</PaginationLink>
</PaginationItem>
)}
{hasStepNext && (
<PaginationItem onClick={() => onPageChange(current + 2)}>
<PaginationLink>{real + 2}</PaginationLink>
</PaginationItem>
)}
{showRightEllipsis && (
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
)}
<PaginationItem onClick={() => hasNext && onPageChange(current + 1)}>
<PaginationNext />
</PaginationItem>
</PaginationContent>
</Pagination>
);
};
PaginationAction.displayName = "PaginationAction";
export {
Pagination,
PaginationContent,
@ -114,4 +199,5 @@ export {
PaginationLink,
PaginationNext,
PaginationPrevious,
PaginationAction,
};

View File

@ -7,7 +7,7 @@ import {
import { syncSiteInfo } from "@/admin/api/info.ts";
import { setAxiosConfig } from "@/conf/api.ts";
export const version = "3.10.3"; // version of the current build
export const version = "3.10.4"; // version of the current build
export const dev: boolean = getDev(); // is in development mode (for debugging, in localhost origin)
export const deploy: boolean = true; // is production environment (for api endpoint)
export const tokenField = getTokenField(deploy); // token field name for storing token

View File

@ -116,7 +116,8 @@ export function setAnnouncement(announcement: string): void {
*/
if (!announcement || announcement.trim() === "") return;
const firstReceived = getMemory("announcement").trim() !== announcement.trim();
const firstReceived =
getMemory("announcement").trim() !== announcement.trim();
setMemory("announcement", announcement);
announcementEvent.emit({

View File

@ -395,11 +395,12 @@
"dashboard": "仪表盘",
"users": "后台管理",
"user": "用户管理",
"broadcast": "公告管理",
"broadcast": "公告通知",
"channel": "渠道设置",
"settings": "系统设置",
"prize": "价格设定",
"subscription": "订阅管理",
"exit": "退出后台",
"billing": "收入",
"billing-today": "今日入账",
"billing-month": "本月入账",

View File

@ -669,7 +669,8 @@
"unban-action": " unBlock User",
"unban-action-desc": "Are you sure you want to unblock this user?",
"billing": "Income",
"chatnio-format-only": "This format is unique to Chat Nio"
"chatnio-format-only": "This format is unique to Chat Nio",
"exit": "Log out of the background"
},
"mask": {
"title": "Mask Settings",

View File

@ -669,7 +669,8 @@
"unban-action": "ユーザーのブロックを解除する",
"unban-action-desc": "このユーザーのブロックを解除してもよろしいですか?",
"billing": "収入",
"chatnio-format-only": "このフォーマットはChat Nioに固有です"
"chatnio-format-only": "このフォーマットはChat Nioに固有です",
"exit": "バックグラウンドからログアウト"
},
"mask": {
"title": "プリセット設定",

View File

@ -669,7 +669,8 @@
"unban-action": "Разблокировать пользователя",
"unban-action-desc": "Вы уверены, что хотите разблокировать этого пользователя?",
"billing": "Доходы",
"chatnio-format-only": "Этот формат уникален для Chat Nio"
"chatnio-format-only": "Этот формат уникален для Chat Nio",
"exit": "Выйти из фонового режима"
},
"mask": {
"title": "Настройки маски",