@thinhnguyencth1204/nextcli 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +632 -90
- package/package.json +1 -1
- package/templates/next-base/components.json +21 -0
- package/templates/next-base/messages/vi/auth.json +28 -0
- package/templates/next-base/messages/vi/common.json +34 -0
- package/templates/next-base/messages/vi/example.json +10 -0
- package/templates/next-base/next.config.ts +11 -1
- package/templates/next-base/nextcli.json +8 -0
- package/templates/next-base/package.json +21 -1
- package/templates/next-base/postcss.config.mjs +5 -0
- package/templates/next-base/src/app/(auth)/layout.tsx +9 -0
- package/templates/next-base/src/app/(auth)/sign-in/page.tsx +6 -3
- package/templates/next-base/src/app/(dashboard)/account/page.tsx +9 -5
- package/templates/next-base/src/app/(dashboard)/dashboard/page.tsx +17 -0
- package/templates/next-base/src/app/(dashboard)/example/page.tsx +5 -2
- package/templates/next-base/src/app/(dashboard)/layout.tsx +10 -0
- package/templates/next-base/src/app/globals.css +107 -0
- package/templates/next-base/src/app/layout.tsx +18 -8
- package/templates/next-base/src/app/page.tsx +2 -18
- package/templates/next-base/src/components/layout/private/app-sidebar.tsx +45 -0
- package/templates/next-base/src/components/layout/private/dashboard-layout.tsx +53 -0
- package/templates/next-base/src/components/layout/private/locale-switcher.tsx +45 -0
- package/templates/next-base/src/components/layout/private/nav-sidebar.tsx +55 -0
- package/templates/next-base/src/components/layout/private/nav-user.tsx +99 -0
- package/templates/next-base/src/components/providers/theme-provider.tsx +11 -0
- package/templates/next-base/src/components/ui/alert-dialog.tsx +11 -0
- package/templates/next-base/src/components/ui/avatar.tsx +45 -0
- package/templates/next-base/src/components/ui/badge.tsx +29 -0
- package/templates/next-base/src/components/ui/button.tsx +47 -7
- package/templates/next-base/src/components/ui/card.tsx +54 -0
- package/templates/next-base/src/components/ui/data-table/data-table-column-header.tsx +23 -0
- package/templates/next-base/src/components/ui/data-table/data-table-filter-list.tsx +3 -0
- package/templates/next-base/src/components/ui/data-table/data-table-pagination.tsx +35 -0
- package/templates/next-base/src/components/ui/data-table/data-table-skeleton.tsx +11 -0
- package/templates/next-base/src/components/ui/data-table/data-table-toolbar.tsx +14 -0
- package/templates/next-base/src/components/ui/data-table/data-table-view-options.tsx +3 -0
- package/templates/next-base/src/components/ui/data-table/data-table.tsx +72 -0
- package/templates/next-base/src/components/ui/dialog.tsx +105 -0
- package/templates/next-base/src/components/ui/dropdown-menu.tsx +44 -0
- package/templates/next-base/src/components/ui/input.tsx +19 -0
- package/templates/next-base/src/components/ui/label.tsx +15 -0
- package/templates/next-base/src/components/ui/popover.tsx +30 -0
- package/templates/next-base/src/components/ui/scroll-area.tsx +47 -0
- package/templates/next-base/src/components/ui/select.tsx +76 -0
- package/templates/next-base/src/components/ui/separator.tsx +23 -0
- package/templates/next-base/src/components/ui/sheet.tsx +117 -0
- package/templates/next-base/src/components/ui/sidebar.tsx +215 -0
- package/templates/next-base/src/components/ui/skeleton.tsx +10 -0
- package/templates/next-base/src/components/ui/sonner.tsx +3 -0
- package/templates/next-base/src/components/ui/table.tsx +54 -0
- package/templates/next-base/src/components/ui/tabs.tsx +52 -0
- package/templates/next-base/src/components/ui/textarea.tsx +17 -0
- package/templates/next-base/src/components/ui/tooltip.tsx +26 -0
- package/templates/next-base/src/data/sidebar-modules.ts +11 -0
- package/templates/next-base/src/example/components/example-table.tsx +25 -40
- package/templates/next-base/src/features/auth/components/account-panel.tsx +21 -8
- package/templates/next-base/src/features/auth/components/sign-in-form.tsx +43 -30
- package/templates/next-base/src/hooks/index.ts +1 -1
- package/templates/next-base/src/hooks/table/use-data-table.ts +33 -0
- package/templates/next-base/src/hooks/use-mobile.ts +25 -0
- package/templates/next-base/src/i18n/config.ts +7 -0
- package/templates/next-base/src/i18n/namespaces.ts +5 -0
- package/templates/next-base/src/i18n/request.ts +19 -2
- package/templates/next-base/src/lib/prisma.ts +11 -1
- package/templates/next-base/src/types/data-table.ts +4 -0
- package/templates/next-base/src/types/index.ts +2 -0
- package/templates/next-base/middleware.ts +0 -10
- package/templates/next-base/src/app/styles.css +0 -12
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
6
|
+
import { LayoutDashboard, Table2, User, type LucideIcon } from "lucide-react";
|
|
7
|
+
import {
|
|
8
|
+
SidebarContent,
|
|
9
|
+
SidebarGroup,
|
|
10
|
+
SidebarGroupContent,
|
|
11
|
+
SidebarGroupLabel,
|
|
12
|
+
SidebarMenu,
|
|
13
|
+
SidebarMenuButton,
|
|
14
|
+
SidebarMenuItem,
|
|
15
|
+
} from "@/components/ui/sidebar";
|
|
16
|
+
import { sidebarModules } from "@/data/sidebar-modules";
|
|
17
|
+
|
|
18
|
+
const icons: Record<string, LucideIcon> = {
|
|
19
|
+
"layout-dashboard": LayoutDashboard,
|
|
20
|
+
table: Table2,
|
|
21
|
+
user: User,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function NavSidebar() {
|
|
25
|
+
const pathname = usePathname();
|
|
26
|
+
const t = useTranslations("common.sidebar");
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<SidebarContent>
|
|
30
|
+
<SidebarGroup>
|
|
31
|
+
<SidebarGroupLabel>{t("groupGeneral")}</SidebarGroupLabel>
|
|
32
|
+
<SidebarGroupContent>
|
|
33
|
+
<SidebarMenu>
|
|
34
|
+
{sidebarModules.map((module) => {
|
|
35
|
+
const Icon = icons[module.icon];
|
|
36
|
+
const isActive =
|
|
37
|
+
pathname === module.url ||
|
|
38
|
+
pathname.startsWith(`${module.url}/`);
|
|
39
|
+
return (
|
|
40
|
+
<SidebarMenuItem key={module.id}>
|
|
41
|
+
<SidebarMenuButton asChild isActive={isActive}>
|
|
42
|
+
<Link href={module.url}>
|
|
43
|
+
<Icon className="h-4 w-4" />
|
|
44
|
+
<span>{t(module.id)}</span>
|
|
45
|
+
</Link>
|
|
46
|
+
</SidebarMenuButton>
|
|
47
|
+
</SidebarMenuItem>
|
|
48
|
+
);
|
|
49
|
+
})}
|
|
50
|
+
</SidebarMenu>
|
|
51
|
+
</SidebarGroupContent>
|
|
52
|
+
</SidebarGroup>
|
|
53
|
+
</SidebarContent>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { LogOut, Monitor, Moon, Sun } from "lucide-react";
|
|
4
|
+
import { useTheme } from "next-themes";
|
|
5
|
+
import { useTranslations } from "next-intl";
|
|
6
|
+
import { authClient } from "@/lib/auth-client";
|
|
7
|
+
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
8
|
+
import { Button } from "@/components/ui/button";
|
|
9
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
10
|
+
import {
|
|
11
|
+
Sheet,
|
|
12
|
+
SheetContent,
|
|
13
|
+
SheetFooter,
|
|
14
|
+
SheetHeader,
|
|
15
|
+
SheetTitle,
|
|
16
|
+
SheetTrigger,
|
|
17
|
+
} from "@/components/ui/sheet";
|
|
18
|
+
import { LocaleSwitcher } from "@/components/layout/private/locale-switcher";
|
|
19
|
+
|
|
20
|
+
export function NavUser() {
|
|
21
|
+
const { theme, setTheme } = useTheme();
|
|
22
|
+
const t = useTranslations("common");
|
|
23
|
+
|
|
24
|
+
const handleLogout = async () => {
|
|
25
|
+
await authClient.signOut();
|
|
26
|
+
window.location.href = "/sign-in";
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Sheet>
|
|
31
|
+
<SheetTrigger asChild>
|
|
32
|
+
<Button variant="ghost" className="h-fit w-fit px-2 py-1">
|
|
33
|
+
<div className="grid text-right text-sm">
|
|
34
|
+
<span className="font-semibold">{t("userMenu.anonymousName")}</span>
|
|
35
|
+
<span className="text-xs text-muted-foreground">
|
|
36
|
+
{t("userMenu.anonymousEmail")}
|
|
37
|
+
</span>
|
|
38
|
+
</div>
|
|
39
|
+
<Avatar className="h-9 w-9">
|
|
40
|
+
<AvatarFallback>U</AvatarFallback>
|
|
41
|
+
</Avatar>
|
|
42
|
+
</Button>
|
|
43
|
+
</SheetTrigger>
|
|
44
|
+
<SheetContent side="right" className="w-full max-w-sm p-0">
|
|
45
|
+
<SheetHeader className="border-b p-4">
|
|
46
|
+
<SheetTitle>{t("userMenu.title")}</SheetTitle>
|
|
47
|
+
</SheetHeader>
|
|
48
|
+
|
|
49
|
+
<ScrollArea className="h-[calc(100vh-10rem)] p-4">
|
|
50
|
+
<div className="space-y-4">
|
|
51
|
+
<div className="rounded-md border bg-card p-4">
|
|
52
|
+
<p className="mb-3 text-sm font-medium">
|
|
53
|
+
{t("userMenu.appearance")}
|
|
54
|
+
</p>
|
|
55
|
+
<div className="grid grid-cols-3 gap-2">
|
|
56
|
+
<Button
|
|
57
|
+
variant={theme === "light" ? "default" : "outline"}
|
|
58
|
+
size="sm"
|
|
59
|
+
onClick={() => setTheme("light")}
|
|
60
|
+
>
|
|
61
|
+
<Sun className="h-4 w-4" />
|
|
62
|
+
{t("header.themeLight")}
|
|
63
|
+
</Button>
|
|
64
|
+
<Button
|
|
65
|
+
variant={theme === "dark" ? "default" : "outline"}
|
|
66
|
+
size="sm"
|
|
67
|
+
onClick={() => setTheme("dark")}
|
|
68
|
+
>
|
|
69
|
+
<Moon className="h-4 w-4" />
|
|
70
|
+
{t("header.themeDark")}
|
|
71
|
+
</Button>
|
|
72
|
+
<Button
|
|
73
|
+
variant={theme === "system" ? "default" : "outline"}
|
|
74
|
+
size="sm"
|
|
75
|
+
onClick={() => setTheme("system")}
|
|
76
|
+
>
|
|
77
|
+
<Monitor className="h-4 w-4" />
|
|
78
|
+
{t("header.themeSystem")}
|
|
79
|
+
</Button>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<LocaleSwitcher />
|
|
83
|
+
</div>
|
|
84
|
+
</ScrollArea>
|
|
85
|
+
|
|
86
|
+
<SheetFooter className="border-t p-0">
|
|
87
|
+
<Button
|
|
88
|
+
variant="ghost"
|
|
89
|
+
className="w-full justify-center rounded-none py-6 text-destructive hover:bg-destructive/10"
|
|
90
|
+
onClick={handleLogout}
|
|
91
|
+
>
|
|
92
|
+
<LogOut className="h-4 w-4" />
|
|
93
|
+
{t("userMenu.logout")}
|
|
94
|
+
</Button>
|
|
95
|
+
</SheetFooter>
|
|
96
|
+
</SheetContent>
|
|
97
|
+
</Sheet>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
5
|
+
|
|
6
|
+
export function ThemeProvider({
|
|
7
|
+
children,
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof NextThemesProvider>) {
|
|
10
|
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
Dialog as AlertDialog,
|
|
5
|
+
DialogTrigger as AlertDialogTrigger,
|
|
6
|
+
DialogContent as AlertDialogContent,
|
|
7
|
+
DialogHeader as AlertDialogHeader,
|
|
8
|
+
DialogFooter as AlertDialogFooter,
|
|
9
|
+
DialogTitle as AlertDialogTitle,
|
|
10
|
+
DialogDescription as AlertDialogDescription,
|
|
11
|
+
} from "@/components/ui/dialog";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
|
3
|
+
import { cn } from "@/utils/cn";
|
|
4
|
+
|
|
5
|
+
export function Avatar({
|
|
6
|
+
className,
|
|
7
|
+
...props
|
|
8
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
|
9
|
+
return (
|
|
10
|
+
<AvatarPrimitive.Root
|
|
11
|
+
className={cn(
|
|
12
|
+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
|
13
|
+
className,
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function AvatarImage({
|
|
21
|
+
className,
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
24
|
+
return (
|
|
25
|
+
<AvatarPrimitive.Image
|
|
26
|
+
className={cn("aspect-square h-full w-full", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function AvatarFallback({
|
|
33
|
+
className,
|
|
34
|
+
...props
|
|
35
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
|
36
|
+
return (
|
|
37
|
+
<AvatarPrimitive.Fallback
|
|
38
|
+
className={cn(
|
|
39
|
+
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
+
import type * as React from "react";
|
|
3
|
+
import { cn } from "@/utils/cn";
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
11
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
|
12
|
+
outline: "text-foreground",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
variant: "default",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export function Badge({
|
|
22
|
+
className,
|
|
23
|
+
variant,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof badgeVariants>) {
|
|
26
|
+
return (
|
|
27
|
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -1,16 +1,56 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
4
|
import { cn } from "@/utils/cn";
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
12
|
+
destructive:
|
|
13
|
+
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
14
|
+
outline:
|
|
15
|
+
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
16
|
+
secondary:
|
|
17
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
18
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
19
|
+
},
|
|
20
|
+
size: {
|
|
21
|
+
default: "h-9 px-4 py-2",
|
|
22
|
+
sm: "h-8 rounded-md px-3",
|
|
23
|
+
lg: "h-10 rounded-md px-8",
|
|
24
|
+
icon: "h-9 w-9",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
variant: "default",
|
|
29
|
+
size: "default",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
|
|
35
|
+
VariantProps<typeof buttonVariants> & {
|
|
36
|
+
asChild?: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function Button({
|
|
40
|
+
className,
|
|
41
|
+
variant,
|
|
42
|
+
size,
|
|
43
|
+
asChild = false,
|
|
44
|
+
...props
|
|
45
|
+
}: ButtonProps) {
|
|
46
|
+
const Comp = asChild ? Slot : "button";
|
|
5
47
|
|
|
6
|
-
export function Button({ className, ...props }: ButtonProps) {
|
|
7
48
|
return (
|
|
8
|
-
<
|
|
9
|
-
className={cn(
|
|
10
|
-
"inline-flex items-center justify-center rounded-md border px-3 py-2 text-sm",
|
|
11
|
-
className,
|
|
12
|
-
)}
|
|
49
|
+
<Comp
|
|
50
|
+
className={cn(buttonVariants({ variant, size }), className)}
|
|
13
51
|
{...props}
|
|
14
52
|
/>
|
|
15
53
|
);
|
|
16
54
|
}
|
|
55
|
+
|
|
56
|
+
export { buttonVariants };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/utils/cn";
|
|
3
|
+
|
|
4
|
+
export function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
5
|
+
return (
|
|
6
|
+
<div
|
|
7
|
+
className={cn(
|
|
8
|
+
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
|
9
|
+
className,
|
|
10
|
+
)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function CardHeader({
|
|
17
|
+
className,
|
|
18
|
+
...props
|
|
19
|
+
}: React.ComponentProps<"div">) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function CardTitle({ className, ...props }: React.ComponentProps<"h3">) {
|
|
29
|
+
return (
|
|
30
|
+
<h3
|
|
31
|
+
className={cn(
|
|
32
|
+
"text-2xl font-semibold leading-none tracking-tight",
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function CardDescription({
|
|
41
|
+
className,
|
|
42
|
+
...props
|
|
43
|
+
}: React.ComponentProps<"p">) {
|
|
44
|
+
return (
|
|
45
|
+
<p className={cn("text-sm text-muted-foreground", className)} {...props} />
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function CardContent({
|
|
50
|
+
className,
|
|
51
|
+
...props
|
|
52
|
+
}: React.ComponentProps<"div">) {
|
|
53
|
+
return <div className={cn("p-6 pt-0", className)} {...props} />;
|
|
54
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { Column } from "@tanstack/react-table";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
|
|
6
|
+
export function DataTableColumnHeader<TData, TValue>({
|
|
7
|
+
column,
|
|
8
|
+
title,
|
|
9
|
+
}: {
|
|
10
|
+
column: Column<TData, TValue>;
|
|
11
|
+
title: string;
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<Button
|
|
15
|
+
variant="ghost"
|
|
16
|
+
size="sm"
|
|
17
|
+
className="h-8 px-2"
|
|
18
|
+
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
|
19
|
+
>
|
|
20
|
+
{title}
|
|
21
|
+
</Button>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { Table } from "@tanstack/react-table";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
|
|
7
|
+
export function DataTablePagination<TData>({ table }: { table: Table<TData> }) {
|
|
8
|
+
const t = useTranslations("common.table");
|
|
9
|
+
const pageCount = table.getPageCount();
|
|
10
|
+
const pageIndex = table.getState().pagination.pageIndex;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="flex items-center justify-end gap-2">
|
|
14
|
+
<span className="text-xs text-muted-foreground">
|
|
15
|
+
{pageCount === 0 ? 0 : pageIndex + 1}/{Math.max(pageCount, 1)}
|
|
16
|
+
</span>
|
|
17
|
+
<Button
|
|
18
|
+
variant="outline"
|
|
19
|
+
size="sm"
|
|
20
|
+
onClick={() => table.previousPage()}
|
|
21
|
+
disabled={!table.getCanPreviousPage()}
|
|
22
|
+
>
|
|
23
|
+
{t("previous")}
|
|
24
|
+
</Button>
|
|
25
|
+
<Button
|
|
26
|
+
variant="outline"
|
|
27
|
+
size="sm"
|
|
28
|
+
onClick={() => table.nextPage()}
|
|
29
|
+
disabled={!table.getCanNextPage()}
|
|
30
|
+
>
|
|
31
|
+
{t("next")}
|
|
32
|
+
</Button>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
2
|
+
|
|
3
|
+
export function DataTableSkeleton() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="space-y-2">
|
|
6
|
+
<Skeleton className="h-8 w-full" />
|
|
7
|
+
<Skeleton className="h-8 w-full" />
|
|
8
|
+
<Skeleton className="h-8 w-full" />
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { Table } from "@tanstack/react-table";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
export function DataTableToolbar<TData>({
|
|
7
|
+
_table,
|
|
8
|
+
children,
|
|
9
|
+
}: {
|
|
10
|
+
_table: Table<TData>;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
}) {
|
|
13
|
+
return <div className="flex items-center justify-between">{children}</div>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { flexRender, type Table as TanstackTable } from "@tanstack/react-table";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination";
|
|
6
|
+
import {
|
|
7
|
+
Table,
|
|
8
|
+
TableBody,
|
|
9
|
+
TableCell,
|
|
10
|
+
TableHead,
|
|
11
|
+
TableHeader,
|
|
12
|
+
TableRow,
|
|
13
|
+
} from "@/components/ui/table";
|
|
14
|
+
|
|
15
|
+
type DataTableProps<TData> = {
|
|
16
|
+
table: TanstackTable<TData>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function DataTable<TData>({ table }: DataTableProps<TData>) {
|
|
20
|
+
const t = useTranslations("common.table");
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="space-y-3">
|
|
24
|
+
<div className="overflow-hidden rounded-md border">
|
|
25
|
+
<Table>
|
|
26
|
+
<TableHeader>
|
|
27
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
28
|
+
<TableRow key={headerGroup.id}>
|
|
29
|
+
{headerGroup.headers.map((header) => (
|
|
30
|
+
<TableHead key={header.id}>
|
|
31
|
+
{header.isPlaceholder
|
|
32
|
+
? null
|
|
33
|
+
: flexRender(
|
|
34
|
+
header.column.columnDef.header,
|
|
35
|
+
header.getContext(),
|
|
36
|
+
)}
|
|
37
|
+
</TableHead>
|
|
38
|
+
))}
|
|
39
|
+
</TableRow>
|
|
40
|
+
))}
|
|
41
|
+
</TableHeader>
|
|
42
|
+
<TableBody>
|
|
43
|
+
{table.getRowModel().rows.length > 0 ? (
|
|
44
|
+
table.getRowModel().rows.map((row) => (
|
|
45
|
+
<TableRow key={row.id}>
|
|
46
|
+
{row.getVisibleCells().map((cell) => (
|
|
47
|
+
<TableCell key={cell.id}>
|
|
48
|
+
{flexRender(
|
|
49
|
+
cell.column.columnDef.cell,
|
|
50
|
+
cell.getContext(),
|
|
51
|
+
)}
|
|
52
|
+
</TableCell>
|
|
53
|
+
))}
|
|
54
|
+
</TableRow>
|
|
55
|
+
))
|
|
56
|
+
) : (
|
|
57
|
+
<TableRow>
|
|
58
|
+
<TableCell
|
|
59
|
+
colSpan={table.getAllColumns().length}
|
|
60
|
+
className="h-24 text-center"
|
|
61
|
+
>
|
|
62
|
+
{t("noResults")}
|
|
63
|
+
</TableCell>
|
|
64
|
+
</TableRow>
|
|
65
|
+
)}
|
|
66
|
+
</TableBody>
|
|
67
|
+
</Table>
|
|
68
|
+
</div>
|
|
69
|
+
<DataTablePagination table={table} />
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
5
|
+
import { X } from "lucide-react";
|
|
6
|
+
import { cn } from "@/utils/cn";
|
|
7
|
+
|
|
8
|
+
export const Dialog = DialogPrimitive.Root;
|
|
9
|
+
export const DialogTrigger = DialogPrimitive.Trigger;
|
|
10
|
+
export const DialogPortal = DialogPrimitive.Portal;
|
|
11
|
+
export const DialogClose = DialogPrimitive.Close;
|
|
12
|
+
|
|
13
|
+
export function DialogOverlay({
|
|
14
|
+
className,
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
17
|
+
return (
|
|
18
|
+
<DialogPrimitive.Overlay
|
|
19
|
+
className={cn("fixed inset-0 z-50 bg-black/50", className)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function DialogContent({
|
|
26
|
+
className,
|
|
27
|
+
children,
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
|
|
30
|
+
return (
|
|
31
|
+
<DialogPortal>
|
|
32
|
+
<DialogOverlay />
|
|
33
|
+
<DialogPrimitive.Content
|
|
34
|
+
className={cn(
|
|
35
|
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg",
|
|
36
|
+
className,
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
>
|
|
40
|
+
{children}
|
|
41
|
+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100">
|
|
42
|
+
<X className="h-4 w-4" />
|
|
43
|
+
<span className="sr-only">Close</span>
|
|
44
|
+
</DialogPrimitive.Close>
|
|
45
|
+
</DialogPrimitive.Content>
|
|
46
|
+
</DialogPortal>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function DialogHeader({
|
|
51
|
+
className,
|
|
52
|
+
...props
|
|
53
|
+
}: React.ComponentProps<"div">) {
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
className={cn(
|
|
57
|
+
"flex flex-col space-y-1.5 text-center sm:text-left",
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function DialogFooter({
|
|
66
|
+
className,
|
|
67
|
+
...props
|
|
68
|
+
}: React.ComponentProps<"div">) {
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
className={cn(
|
|
72
|
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
73
|
+
className,
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function DialogTitle({
|
|
81
|
+
className,
|
|
82
|
+
...props
|
|
83
|
+
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
84
|
+
return (
|
|
85
|
+
<DialogPrimitive.Title
|
|
86
|
+
className={cn(
|
|
87
|
+
"text-lg font-semibold leading-none tracking-tight",
|
|
88
|
+
className,
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function DialogDescription({
|
|
96
|
+
className,
|
|
97
|
+
...props
|
|
98
|
+
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
99
|
+
return (
|
|
100
|
+
<DialogPrimitive.Description
|
|
101
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|