@goplusvn/core 0.1.1 → 0.1.3
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/package.json +31 -175
- package/dist/audit/index.d.mts +0 -115
- package/dist/audit/index.d.ts +0 -115
- package/dist/audit/index.js +0 -204
- package/dist/audit/index.js.map +0 -1
- package/dist/audit/index.mjs +0 -200
- package/dist/audit/index.mjs.map +0 -1
- package/dist/auth/index.d.mts +0 -86
- package/dist/auth/index.d.ts +0 -86
- package/dist/auth/index.js +0 -210
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/index.mjs +0 -198
- package/dist/auth/index.mjs.map +0 -1
- package/dist/button-1dWvP9Ib.d.mts +0 -30
- package/dist/button-1dWvP9Ib.d.ts +0 -30
- package/dist/calendar-2QzdEo1z.d.mts +0 -20
- package/dist/calendar-2QzdEo1z.d.ts +0 -20
- package/dist/code-generation/index.d.mts +0 -30
- package/dist/code-generation/index.d.ts +0 -30
- package/dist/code-generation/index.js +0 -31
- package/dist/code-generation/index.js.map +0 -1
- package/dist/code-generation/index.mjs +0 -28
- package/dist/code-generation/index.mjs.map +0 -1
- package/dist/configs/index.d.mts +0 -175
- package/dist/configs/index.d.ts +0 -175
- package/dist/configs/index.js +0 -254
- package/dist/configs/index.js.map +0 -1
- package/dist/configs/index.mjs +0 -233
- package/dist/configs/index.mjs.map +0 -1
- package/dist/crud/index.d.mts +0 -646
- package/dist/crud/index.d.ts +0 -646
- package/dist/crud/index.js +0 -11772
- package/dist/crud/index.js.map +0 -1
- package/dist/crud/index.mjs +0 -11665
- package/dist/crud/index.mjs.map +0 -1
- package/dist/crud/server.d.mts +0 -20
- package/dist/crud/server.d.ts +0 -20
- package/dist/crud/server.js +0 -123
- package/dist/crud/server.js.map +0 -1
- package/dist/crud/server.mjs +0 -120
- package/dist/crud/server.mjs.map +0 -1
- package/dist/data-table-skeleton-12NA8Mjx.d.mts +0 -39
- package/dist/data-table-skeleton-12NA8Mjx.d.ts +0 -39
- package/dist/dialog-bKfjZMTd.d.mts +0 -22
- package/dist/dialog-bKfjZMTd.d.ts +0 -22
- package/dist/dynamic-icon-DrGIiu2N.d.mts +0 -10
- package/dist/dynamic-icon-DrGIiu2N.d.ts +0 -10
- package/dist/home/index.d.mts +0 -269
- package/dist/home/index.d.ts +0 -269
- package/dist/home/index.js +0 -1678
- package/dist/home/index.js.map +0 -1
- package/dist/home/index.mjs +0 -1635
- package/dist/home/index.mjs.map +0 -1
- package/dist/hooks/index.d.mts +0 -7
- package/dist/hooks/index.d.ts +0 -7
- package/dist/hooks/index.js +0 -8316
- package/dist/hooks/index.js.map +0 -1
- package/dist/hooks/index.mjs +0 -8255
- package/dist/hooks/index.mjs.map +0 -1
- package/dist/index-50hpiPrV.d.ts +0 -116
- package/dist/index-B9zQVEVi.d.mts +0 -116
- package/dist/index.d.mts +0 -5
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -123
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -118
- package/dist/index.mjs.map +0 -1
- package/dist/infrastructure/index.d.mts +0 -423
- package/dist/infrastructure/index.d.ts +0 -423
- package/dist/infrastructure/index.js +0 -633
- package/dist/infrastructure/index.js.map +0 -1
- package/dist/infrastructure/index.mjs +0 -619
- package/dist/infrastructure/index.mjs.map +0 -1
- package/dist/label-DWTEkNPo.d.ts +0 -226
- package/dist/label-LPpdcoBx.d.mts +0 -226
- package/dist/layout/index.d.mts +0 -48
- package/dist/layout/index.d.ts +0 -48
- package/dist/layout/index.js +0 -117
- package/dist/layout/index.js.map +0 -1
- package/dist/layout/index.mjs +0 -90
- package/dist/layout/index.mjs.map +0 -1
- package/dist/navigation/index.d.mts +0 -16
- package/dist/navigation/index.d.ts +0 -16
- package/dist/navigation/index.js +0 -53
- package/dist/navigation/index.js.map +0 -1
- package/dist/navigation/index.mjs +0 -50
- package/dist/navigation/index.mjs.map +0 -1
- package/dist/notification/index.d.mts +0 -105
- package/dist/notification/index.d.ts +0 -105
- package/dist/notification/index.js +0 -278
- package/dist/notification/index.js.map +0 -1
- package/dist/notification/index.mjs +0 -274
- package/dist/notification/index.mjs.map +0 -1
- package/dist/organization/index.d.mts +0 -99
- package/dist/organization/index.d.ts +0 -99
- package/dist/organization/index.js +0 -360
- package/dist/organization/index.js.map +0 -1
- package/dist/organization/index.mjs +0 -352
- package/dist/organization/index.mjs.map +0 -1
- package/dist/plugin/index.d.mts +0 -83
- package/dist/plugin/index.d.ts +0 -83
- package/dist/plugin/index.js +0 -86
- package/dist/plugin/index.js.map +0 -1
- package/dist/plugin/index.mjs +0 -84
- package/dist/plugin/index.mjs.map +0 -1
- package/dist/providers/index.d.mts +0 -25
- package/dist/providers/index.d.ts +0 -25
- package/dist/providers/index.js +0 -84
- package/dist/providers/index.js.map +0 -1
- package/dist/providers/index.mjs +0 -77
- package/dist/providers/index.mjs.map +0 -1
- package/dist/rbac/index.d.mts +0 -226
- package/dist/rbac/index.d.ts +0 -226
- package/dist/rbac/index.js +0 -4784
- package/dist/rbac/index.js.map +0 -1
- package/dist/rbac/index.mjs +0 -4722
- package/dist/rbac/index.mjs.map +0 -1
- package/dist/rbac/permissions.d.mts +0 -26
- package/dist/rbac/permissions.d.ts +0 -26
- package/dist/rbac/permissions.js +0 -94
- package/dist/rbac/permissions.js.map +0 -1
- package/dist/rbac/permissions.mjs +0 -90
- package/dist/rbac/permissions.mjs.map +0 -1
- package/dist/rbac/server.d.mts +0 -1
- package/dist/rbac/server.d.ts +0 -1
- package/dist/rbac/server.js +0 -128
- package/dist/rbac/server.js.map +0 -1
- package/dist/rbac/server.mjs +0 -124
- package/dist/rbac/server.mjs.map +0 -1
- package/dist/schemas/index.d.mts +0 -1257
- package/dist/schemas/index.d.ts +0 -1257
- package/dist/schemas/index.js +0 -572
- package/dist/schemas/index.js.map +0 -1
- package/dist/schemas/index.mjs +0 -523
- package/dist/schemas/index.mjs.map +0 -1
- package/dist/server-QuYCTa89.d.mts +0 -83
- package/dist/server-QuYCTa89.d.ts +0 -83
- package/dist/sonner-C74GlRDQ.d.mts +0 -71
- package/dist/sonner-C74GlRDQ.d.ts +0 -71
- package/dist/status-BOXZgIqX.d.mts +0 -12
- package/dist/status-BOXZgIqX.d.ts +0 -12
- package/dist/system/index.d.mts +0 -77
- package/dist/system/index.d.ts +0 -77
- package/dist/system/index.js +0 -102
- package/dist/system/index.js.map +0 -1
- package/dist/system/index.mjs +0 -100
- package/dist/system/index.mjs.map +0 -1
- package/dist/tabs-C6FfBwPY.d.mts +0 -18
- package/dist/tabs-C6FfBwPY.d.ts +0 -18
- package/dist/tenant-provider-B8eC_Wpb.d.mts +0 -27
- package/dist/tenant-provider-B8eC_Wpb.d.ts +0 -27
- package/dist/types/index.d.mts +0 -469
- package/dist/types/index.d.ts +0 -469
- package/dist/types/index.js +0 -25
- package/dist/types/index.js.map +0 -1
- package/dist/types/index.mjs +0 -21
- package/dist/types/index.mjs.map +0 -1
- package/dist/ui/auth.d.mts +0 -39
- package/dist/ui/auth.d.ts +0 -39
- package/dist/ui/auth.js +0 -4941
- package/dist/ui/auth.js.map +0 -1
- package/dist/ui/auth.mjs +0 -4896
- package/dist/ui/auth.mjs.map +0 -1
- package/dist/ui/crud.d.mts +0 -2
- package/dist/ui/crud.d.ts +0 -2
- package/dist/ui/crud.js +0 -4
- package/dist/ui/crud.js.map +0 -1
- package/dist/ui/crud.mjs +0 -3
- package/dist/ui/crud.mjs.map +0 -1
- package/dist/ui/data-display.d.mts +0 -596
- package/dist/ui/data-display.d.ts +0 -596
- package/dist/ui/data-display.js +0 -5307
- package/dist/ui/data-display.js.map +0 -1
- package/dist/ui/data-display.mjs +0 -5212
- package/dist/ui/data-display.mjs.map +0 -1
- package/dist/ui/feedback.d.mts +0 -55
- package/dist/ui/feedback.d.ts +0 -55
- package/dist/ui/feedback.js +0 -2608
- package/dist/ui/feedback.js.map +0 -1
- package/dist/ui/feedback.mjs +0 -2526
- package/dist/ui/feedback.mjs.map +0 -1
- package/dist/ui/forms.d.mts +0 -309
- package/dist/ui/forms.d.ts +0 -309
- package/dist/ui/forms.js +0 -4656
- package/dist/ui/forms.js.map +0 -1
- package/dist/ui/forms.mjs +0 -4571
- package/dist/ui/forms.mjs.map +0 -1
- package/dist/ui/index.d.mts +0 -331
- package/dist/ui/index.d.ts +0 -331
- package/dist/ui/index.js +0 -16953
- package/dist/ui/index.js.map +0 -1
- package/dist/ui/index.mjs +0 -16598
- package/dist/ui/index.mjs.map +0 -1
- package/dist/ui/primitives/client.d.mts +0 -61
- package/dist/ui/primitives/client.d.ts +0 -61
- package/dist/ui/primitives/client.js +0 -3408
- package/dist/ui/primitives/client.js.map +0 -1
- package/dist/ui/primitives/client.mjs +0 -3256
- package/dist/ui/primitives/client.mjs.map +0 -1
- package/dist/ui/primitives.d.mts +0 -113
- package/dist/ui/primitives.d.ts +0 -113
- package/dist/ui/primitives.js +0 -3356
- package/dist/ui/primitives.js.map +0 -1
- package/dist/ui/primitives.mjs +0 -3227
- package/dist/ui/primitives.mjs.map +0 -1
- package/dist/user/index.d.mts +0 -228
- package/dist/user/index.d.ts +0 -228
- package/dist/user/index.js +0 -4306
- package/dist/user/index.js.map +0 -1
- package/dist/user/index.mjs +0 -4260
- package/dist/user/index.mjs.map +0 -1
- package/dist/utils/index.d.mts +0 -205
- package/dist/utils/index.d.ts +0 -205
- package/dist/utils/index.js +0 -574
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/index.mjs +0 -514
- package/dist/utils/index.mjs.map +0 -1
- package/dist/workflow/index.d.mts +0 -40
- package/dist/workflow/index.d.ts +0 -40
- package/dist/workflow/index.js +0 -3710
- package/dist/workflow/index.js.map +0 -1
- package/dist/workflow/index.mjs +0 -3677
- package/dist/workflow/index.mjs.map +0 -1
package/dist/home/index.mjs
DELETED
|
@@ -1,1635 +0,0 @@
|
|
|
1
|
-
import { useRouter } from 'next/navigation';
|
|
2
|
-
import useSWR from 'swr';
|
|
3
|
-
import { TrendingUp, Zap, GripVertical, Eye, EyeOff, Settings, X, DollarSign, ShoppingCart, Users, Package, AlertTriangle, RotateCcw, Plus, ChevronLeft, ChevronRight, Pencil } from 'lucide-react';
|
|
4
|
-
import { clsx } from 'clsx';
|
|
5
|
-
import { twMerge } from 'tailwind-merge';
|
|
6
|
-
import { format } from 'date-fns';
|
|
7
|
-
import { vi } from 'date-fns/locale';
|
|
8
|
-
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
|
-
import { useState, useCallback, useEffect } from 'react';
|
|
10
|
-
import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter } from '@dnd-kit/core';
|
|
11
|
-
import { useSortable, sortableKeyboardCoordinates, arrayMove, SortableContext, rectSortingStrategy } from '@dnd-kit/sortable';
|
|
12
|
-
import { motion, AnimatePresence } from 'framer-motion';
|
|
13
|
-
import { CSS } from '@dnd-kit/utilities';
|
|
14
|
-
import useEmblaCarousel from 'embla-carousel-react';
|
|
15
|
-
import Autoplay from 'embla-carousel-autoplay';
|
|
16
|
-
import { useForm } from 'react-hook-form';
|
|
17
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
18
|
-
import * as z from 'zod';
|
|
19
|
-
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
20
|
-
|
|
21
|
-
// src/home/home-page.tsx
|
|
22
|
-
function cn(...inputs) {
|
|
23
|
-
return twMerge(clsx(inputs));
|
|
24
|
-
}
|
|
25
|
-
function getGreeting(hour) {
|
|
26
|
-
if (hour >= 5 && hour < 12) {
|
|
27
|
-
return { text: "Ch\xE0o bu\u1ED5i s\xE1ng", emoji: "\u{1F305}" };
|
|
28
|
-
} else if (hour >= 12 && hour < 18) {
|
|
29
|
-
return { text: "Ch\xE0o bu\u1ED5i chi\u1EC1u", emoji: "\u2600\uFE0F" };
|
|
30
|
-
} else {
|
|
31
|
-
return { text: "Ch\xE0o bu\u1ED5i t\u1ED1i", emoji: "\u{1F319}" };
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function WelcomeCard({ userName = "B\u1EA1n", className }) {
|
|
35
|
-
const now = /* @__PURE__ */ new Date();
|
|
36
|
-
const hour = now.getHours();
|
|
37
|
-
const greeting = getGreeting(hour);
|
|
38
|
-
const formattedDate = format(now, "EEEE, dd/MM/yyyy", { locale: vi });
|
|
39
|
-
const formattedTime = format(now, "HH:mm");
|
|
40
|
-
return /* @__PURE__ */ jsxs(
|
|
41
|
-
"div",
|
|
42
|
-
{
|
|
43
|
-
className: cn(
|
|
44
|
-
"w-full bg-gradient-to-r from-blue-900 to-primary rounded-xl overflow-hidden shadow-lg group relative animate-in fade-in slide-in-from-bottom-4 duration-500",
|
|
45
|
-
className
|
|
46
|
-
),
|
|
47
|
-
children: [
|
|
48
|
-
/* @__PURE__ */ jsx(
|
|
49
|
-
"div",
|
|
50
|
-
{
|
|
51
|
-
className: "absolute inset-0 opacity-20 pointer-events-none",
|
|
52
|
-
style: {
|
|
53
|
-
backgroundImage: "radial-gradient(circle at 80% 20%, white 0%, transparent 40%), radial-gradient(circle at 10% 80%, white 0%, transparent 30%)"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
),
|
|
57
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row items-center relative overflow-hidden", children: [
|
|
58
|
-
/* @__PURE__ */ jsxs("div", { className: "p-8 flex-1 z-10 w-full", children: [
|
|
59
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 mb-4", children: /* @__PURE__ */ jsx("span", { className: "bg-white/20 text-white text-xs font-bold px-2 py-1 rounded uppercase tracking-wider backdrop-blur-sm", children: formattedDate }) }),
|
|
60
|
-
/* @__PURE__ */ jsxs("h2", { className: "text-white text-2xl md:text-3xl font-bold mb-3", children: [
|
|
61
|
-
greeting.text,
|
|
62
|
-
", ",
|
|
63
|
-
userName
|
|
64
|
-
] }),
|
|
65
|
-
/* @__PURE__ */ jsx("div", { className: "text-blue-100 mb-6 max-w-lg", children: "Ch\xFAc b\u1EA1n m\u1ED9t ng\xE0y l\xE0m vi\u1EC7c hi\u1EC7u qu\u1EA3. H\u1EC7 th\u1ED1ng \u0111ang ho\u1EA1t \u0111\u1ED9ng \u1ED5n \u0111\u1ECBnh." }),
|
|
66
|
-
/* @__PURE__ */ jsx("div", { className: "flex gap-3", children: /* @__PURE__ */ jsxs("div", { className: "bg-white/10 text-white px-5 py-2.5 rounded-lg text-sm font-bold shadow-sm backdrop-blur-sm border border-white/20 flex items-center gap-2", children: [
|
|
67
|
-
/* @__PURE__ */ jsx("span", { className: "w-2 h-2 rounded-full bg-green-400 animate-pulse" }),
|
|
68
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
69
|
-
"Online \u2022 ",
|
|
70
|
-
formattedTime
|
|
71
|
-
] })
|
|
72
|
-
] }) })
|
|
73
|
-
] }),
|
|
74
|
-
/* @__PURE__ */ jsx("div", { className: "hidden md:flex flex-1 justify-end items-end h-full p-8 z-10 self-stretch", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 opacity-90 transform translate-y-4", children: [
|
|
75
|
-
/* @__PURE__ */ jsxs("div", { className: "bg-white/10 backdrop-blur-md border border-white/20 p-3 rounded-lg flex items-center gap-3", children: [
|
|
76
|
-
/* @__PURE__ */ jsx(TrendingUp, { className: "text-white w-6 h-6" }),
|
|
77
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
78
|
-
/* @__PURE__ */ jsx("div", { className: "h-1.5 w-12 bg-white/40 rounded mb-1" }),
|
|
79
|
-
/* @__PURE__ */ jsx("div", { className: "h-1.5 w-8 bg-white/20 rounded" })
|
|
80
|
-
] })
|
|
81
|
-
] }),
|
|
82
|
-
/* @__PURE__ */ jsxs("div", { className: "bg-white/10 backdrop-blur-md border border-white/20 p-3 rounded-lg flex items-center gap-3", children: [
|
|
83
|
-
/* @__PURE__ */ jsx(Zap, { className: "text-white w-6 h-6" }),
|
|
84
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
85
|
-
/* @__PURE__ */ jsx("div", { className: "h-1.5 w-12 bg-white/40 rounded mb-1" }),
|
|
86
|
-
/* @__PURE__ */ jsx("div", { className: "h-1.5 w-8 bg-white/20 rounded" })
|
|
87
|
-
] })
|
|
88
|
-
] })
|
|
89
|
-
] }) })
|
|
90
|
-
] })
|
|
91
|
-
]
|
|
92
|
-
}
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// src/home/types.ts
|
|
97
|
-
var DEFAULT_WIDGETS = [
|
|
98
|
-
{
|
|
99
|
-
id: "revenue",
|
|
100
|
-
type: "revenue",
|
|
101
|
-
position: 0,
|
|
102
|
-
visible: true,
|
|
103
|
-
size: "medium"
|
|
104
|
-
},
|
|
105
|
-
{ id: "orders", type: "orders", position: 1, visible: true, size: "medium" },
|
|
106
|
-
{
|
|
107
|
-
id: "customers",
|
|
108
|
-
type: "customers",
|
|
109
|
-
position: 2,
|
|
110
|
-
visible: true,
|
|
111
|
-
size: "medium"
|
|
112
|
-
},
|
|
113
|
-
{ id: "stock", type: "stock", position: 3, visible: true, size: "medium" }
|
|
114
|
-
];
|
|
115
|
-
var WIDGET_LABELS = {
|
|
116
|
-
revenue: "Doanh thu",
|
|
117
|
-
orders: "\u0110\u01A1n h\xE0ng",
|
|
118
|
-
customers: "Kh\xE1ch h\xE0ng",
|
|
119
|
-
stock: "T\u1ED3n kho"
|
|
120
|
-
};
|
|
121
|
-
var ERP_FEATURES = [
|
|
122
|
-
{
|
|
123
|
-
id: "inventory",
|
|
124
|
-
icon: "\u{1F4E6}",
|
|
125
|
-
title: "Qu\u1EA3n l\xFD Kho",
|
|
126
|
-
description: "Qu\u1EA3n l\xFD to\xE0n di\u1EC7n h\xE0ng h\xF3a v\xE0 t\u1ED3n kho",
|
|
127
|
-
subFeatures: ["Nh\u1EADp/Xu\u1EA5t kho", "Ki\u1EC3m k\xEA", "\u0110i\u1EC1u chuy\u1EC3n", "C\u1EA3nh b\xE1o t\u1ED3n"],
|
|
128
|
-
color: "from-blue-500 to-cyan-500"
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
id: "sales",
|
|
132
|
-
icon: "\u{1F4B0}",
|
|
133
|
-
title: "Qu\u1EA3n l\xFD B\xE1n h\xE0ng",
|
|
134
|
-
description: "X\u1EED l\xFD \u0111\u01A1n h\xE0ng v\xE0 b\xE1n h\xE0ng hi\u1EC7u qu\u1EA3",
|
|
135
|
-
subFeatures: ["POS", "\u0110\u01A1n h\xE0ng", "H\u1EE3p \u0111\u1ED3ng", "B\xE1o gi\xE1"],
|
|
136
|
-
color: "from-green-500 to-emerald-500"
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
id: "purchasing",
|
|
140
|
-
icon: "\u{1F6D2}",
|
|
141
|
-
title: "Qu\u1EA3n l\xFD Mua h\xE0ng",
|
|
142
|
-
description: "Qu\u1EA3n l\xFD nh\xE0 cung c\u1EA5p v\xE0 \u0111\u1EB7t h\xE0ng",
|
|
143
|
-
subFeatures: ["\u0110\u1EB7t h\xE0ng NCC", "Nh\u1EADp h\xE0ng", "C\xF4ng n\u1EE3 ph\u1EA3i tr\u1EA3"],
|
|
144
|
-
color: "from-orange-500 to-amber-500"
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
id: "finance",
|
|
148
|
-
icon: "\u{1F4B5}",
|
|
149
|
-
title: "T\xE0i ch\xEDnh",
|
|
150
|
-
description: "Qu\u1EA3n l\xFD d\xF2ng ti\u1EC1n v\xE0 c\xF4ng n\u1EE3",
|
|
151
|
-
subFeatures: ["Thu/Chi", "S\u1ED5 qu\u1EF9", "C\xF4ng n\u1EE3", "B\xE1o c\xE1o t\xE0i ch\xEDnh"],
|
|
152
|
-
color: "from-purple-500 to-violet-500"
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
id: "crm",
|
|
156
|
-
icon: "\u{1F465}",
|
|
157
|
-
title: "CRM",
|
|
158
|
-
description: "Ch\u0103m s\xF3c v\xE0 qu\u1EA3n l\xFD kh\xE1ch h\xE0ng",
|
|
159
|
-
subFeatures: ["Kh\xE1ch h\xE0ng", "L\u1ECBch s\u1EED giao d\u1ECBch", "Ph\xE2n lo\u1EA1i"],
|
|
160
|
-
color: "from-pink-500 to-rose-500"
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
id: "hr",
|
|
164
|
-
icon: "\u{1F468}\u200D\u{1F4BC}",
|
|
165
|
-
title: "Nh\xE2n s\u1EF1",
|
|
166
|
-
description: "Qu\u1EA3n l\xFD ng\u01B0\u1EDDi d\xF9ng v\xE0 ph\xE2n quy\u1EC1n",
|
|
167
|
-
subFeatures: ["Users", "Ph\xE2n quy\u1EC1n", "Roles"],
|
|
168
|
-
color: "from-indigo-500 to-blue-500"
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
id: "reports",
|
|
172
|
-
icon: "\u{1F4CA}",
|
|
173
|
-
title: "B\xE1o c\xE1o",
|
|
174
|
-
description: "Th\u1ED1ng k\xEA v\xE0 ph\xE2n t\xEDch d\u1EEF li\u1EC7u",
|
|
175
|
-
subFeatures: ["Dashboard", "Th\u1ED1ng k\xEA", "Export"],
|
|
176
|
-
color: "from-teal-500 to-green-500"
|
|
177
|
-
}
|
|
178
|
-
];
|
|
179
|
-
var sizeClasses = {
|
|
180
|
-
small: "col-span-1",
|
|
181
|
-
medium: "col-span-1 md:col-span-1",
|
|
182
|
-
large: "col-span-1 md:col-span-2"
|
|
183
|
-
};
|
|
184
|
-
function BaseWidget({
|
|
185
|
-
config,
|
|
186
|
-
title,
|
|
187
|
-
children,
|
|
188
|
-
loading = false,
|
|
189
|
-
error,
|
|
190
|
-
onRemove,
|
|
191
|
-
onToggleVisibility,
|
|
192
|
-
onSettings,
|
|
193
|
-
className,
|
|
194
|
-
isDragging = false
|
|
195
|
-
}) {
|
|
196
|
-
const {
|
|
197
|
-
attributes,
|
|
198
|
-
listeners,
|
|
199
|
-
setNodeRef,
|
|
200
|
-
transform,
|
|
201
|
-
transition,
|
|
202
|
-
isDragging: isSortableDragging
|
|
203
|
-
} = useSortable({ id: config.id });
|
|
204
|
-
const style = {
|
|
205
|
-
transform: CSS.Transform.toString(transform),
|
|
206
|
-
transition
|
|
207
|
-
};
|
|
208
|
-
const isCurrentlyDragging = isDragging || isSortableDragging;
|
|
209
|
-
if (!config.visible) {
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
return /* @__PURE__ */ jsxs(
|
|
213
|
-
motion.div,
|
|
214
|
-
{
|
|
215
|
-
ref: setNodeRef,
|
|
216
|
-
style,
|
|
217
|
-
initial: { opacity: 0, scale: 0.95 },
|
|
218
|
-
animate: { opacity: 1, scale: 1 },
|
|
219
|
-
exit: { opacity: 0, scale: 0.95 },
|
|
220
|
-
className: cn(
|
|
221
|
-
"group relative flex flex-col justify-between gap-3 rounded-xl border border-slate-200 bg-white p-5 shadow-sm transition-all hover:border-primary/30 hover:shadow-md dark:border-slate-700 dark:bg-slate-800",
|
|
222
|
-
sizeClasses[config.size],
|
|
223
|
-
isCurrentlyDragging && "z-50 shadow-lg ring-2 ring-primary/20",
|
|
224
|
-
className
|
|
225
|
-
),
|
|
226
|
-
children: [
|
|
227
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1", children: [
|
|
228
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
229
|
-
/* @__PURE__ */ jsx(
|
|
230
|
-
"button",
|
|
231
|
-
{
|
|
232
|
-
...attributes,
|
|
233
|
-
...listeners,
|
|
234
|
-
className: "cursor-grab touch-none rounded text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 active:cursor-grabbing opacity-0 group-hover:opacity-100 transition-opacity",
|
|
235
|
-
"aria-label": "Drag to reorder",
|
|
236
|
-
children: /* @__PURE__ */ jsx(GripVertical, { className: "h-4 w-4" })
|
|
237
|
-
}
|
|
238
|
-
),
|
|
239
|
-
/* @__PURE__ */ jsx("h4", { className: "flex items-center gap-2 text-sm font-semibold text-slate-900 dark:text-white", children: title })
|
|
240
|
-
] }),
|
|
241
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100", children: [
|
|
242
|
-
onToggleVisibility && /* @__PURE__ */ jsx(
|
|
243
|
-
"button",
|
|
244
|
-
{
|
|
245
|
-
onClick: onToggleVisibility,
|
|
246
|
-
className: "rounded p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-700 dark:hover:bg-slate-700 dark:hover:text-slate-300",
|
|
247
|
-
"aria-label": config.visible ? "Hide widget" : "Show widget",
|
|
248
|
-
children: config.visible ? /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(EyeOff, { className: "h-4 w-4" })
|
|
249
|
-
}
|
|
250
|
-
),
|
|
251
|
-
onSettings && /* @__PURE__ */ jsx(
|
|
252
|
-
"button",
|
|
253
|
-
{
|
|
254
|
-
onClick: onSettings,
|
|
255
|
-
className: "rounded p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-700 dark:hover:bg-slate-700 dark:hover:text-slate-300",
|
|
256
|
-
"aria-label": "Widget settings",
|
|
257
|
-
children: /* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" })
|
|
258
|
-
}
|
|
259
|
-
),
|
|
260
|
-
onRemove && /* @__PURE__ */ jsx(
|
|
261
|
-
"button",
|
|
262
|
-
{
|
|
263
|
-
onClick: onRemove,
|
|
264
|
-
className: "rounded p-1 text-slate-400 hover:bg-red-50 hover:text-red-500 dark:hover:bg-red-900/20",
|
|
265
|
-
"aria-label": "Remove widget",
|
|
266
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
267
|
-
}
|
|
268
|
-
)
|
|
269
|
-
] })
|
|
270
|
-
] }),
|
|
271
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1", children: loading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-4", children: /* @__PURE__ */ jsx("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" }) }) : error ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-4 text-xs text-red-500", children: error }) : children })
|
|
272
|
-
]
|
|
273
|
-
}
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
function WidgetStat({
|
|
277
|
-
value,
|
|
278
|
-
label,
|
|
279
|
-
change,
|
|
280
|
-
valueClassName
|
|
281
|
-
}) {
|
|
282
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
283
|
-
/* @__PURE__ */ jsx("div", { className: cn("text-3xl font-bold text-foreground", valueClassName), children: value }),
|
|
284
|
-
label && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: label }),
|
|
285
|
-
change && /* @__PURE__ */ jsxs(
|
|
286
|
-
"div",
|
|
287
|
-
{
|
|
288
|
-
className: cn(
|
|
289
|
-
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium",
|
|
290
|
-
change.type === "increase" ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
|
|
291
|
-
),
|
|
292
|
-
children: [
|
|
293
|
-
change.type === "increase" ? /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", viewBox: "0 0 12 12", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M6 2L10 7H2L6 2Z" }) }) : /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", viewBox: "0 0 12 12", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M6 10L2 5H10L6 10Z" }) }),
|
|
294
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
295
|
-
change.value > 0 ? "+" : "",
|
|
296
|
-
change.value,
|
|
297
|
-
"%"
|
|
298
|
-
] }),
|
|
299
|
-
change.period && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
300
|
-
"vs ",
|
|
301
|
-
change.period
|
|
302
|
-
] })
|
|
303
|
-
]
|
|
304
|
-
}
|
|
305
|
-
)
|
|
306
|
-
] });
|
|
307
|
-
}
|
|
308
|
-
function formatCurrency(value) {
|
|
309
|
-
return new Intl.NumberFormat("vi-VN", {
|
|
310
|
-
style: "currency",
|
|
311
|
-
currency: "VND",
|
|
312
|
-
maximumFractionDigits: 0
|
|
313
|
-
}).format(value);
|
|
314
|
-
}
|
|
315
|
-
function RevenueWidget({
|
|
316
|
-
config,
|
|
317
|
-
data,
|
|
318
|
-
loading,
|
|
319
|
-
error,
|
|
320
|
-
onRemove,
|
|
321
|
-
onToggleVisibility,
|
|
322
|
-
onSettings
|
|
323
|
-
}) {
|
|
324
|
-
const formattedValue = data ? formatCurrency(data.total) : "0 \u20AB";
|
|
325
|
-
return /* @__PURE__ */ jsx(
|
|
326
|
-
BaseWidget,
|
|
327
|
-
{
|
|
328
|
-
config,
|
|
329
|
-
title: "Doanh thu",
|
|
330
|
-
loading,
|
|
331
|
-
error,
|
|
332
|
-
onRemove,
|
|
333
|
-
onToggleVisibility,
|
|
334
|
-
onSettings,
|
|
335
|
-
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
|
|
336
|
-
/* @__PURE__ */ jsx(
|
|
337
|
-
WidgetStat,
|
|
338
|
-
{
|
|
339
|
-
value: formattedValue,
|
|
340
|
-
label: "H\xF4m nay",
|
|
341
|
-
change: data?.change !== void 0 ? {
|
|
342
|
-
value: data.change,
|
|
343
|
-
type: data.change >= 0 ? "increase" : "decrease",
|
|
344
|
-
period: data.changePeriod || "h\xF4m qua"
|
|
345
|
-
} : void 0,
|
|
346
|
-
valueClassName: "text-green-600 dark:text-green-400"
|
|
347
|
-
}
|
|
348
|
-
),
|
|
349
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-green-100 p-3 dark:bg-green-900/30", children: /* @__PURE__ */ jsx(DollarSign, { className: "h-6 w-6 text-green-600 dark:text-green-400" }) })
|
|
350
|
-
] })
|
|
351
|
-
}
|
|
352
|
-
);
|
|
353
|
-
}
|
|
354
|
-
function OrdersWidget({
|
|
355
|
-
config,
|
|
356
|
-
data,
|
|
357
|
-
loading,
|
|
358
|
-
error,
|
|
359
|
-
onRemove,
|
|
360
|
-
onToggleVisibility,
|
|
361
|
-
onSettings
|
|
362
|
-
}) {
|
|
363
|
-
return /* @__PURE__ */ jsx(
|
|
364
|
-
BaseWidget,
|
|
365
|
-
{
|
|
366
|
-
config,
|
|
367
|
-
title: "\u0110\u01A1n h\xE0ng",
|
|
368
|
-
loading,
|
|
369
|
-
error,
|
|
370
|
-
onRemove,
|
|
371
|
-
onToggleVisibility,
|
|
372
|
-
onSettings,
|
|
373
|
-
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
|
|
374
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
375
|
-
/* @__PURE__ */ jsx(
|
|
376
|
-
WidgetStat,
|
|
377
|
-
{
|
|
378
|
-
value: data?.total ?? 0,
|
|
379
|
-
label: "\u0110\u01A1n h\xF4m nay",
|
|
380
|
-
change: data?.change !== void 0 ? {
|
|
381
|
-
value: data.change,
|
|
382
|
-
type: data.change >= 0 ? "increase" : "decrease",
|
|
383
|
-
period: data.changePeriod || "h\xF4m qua"
|
|
384
|
-
} : void 0,
|
|
385
|
-
valueClassName: "text-blue-600 dark:text-blue-400"
|
|
386
|
-
}
|
|
387
|
-
),
|
|
388
|
-
(data?.pending !== void 0 || data?.completed !== void 0) && /* @__PURE__ */ jsxs("div", { className: "flex gap-4 text-sm", children: [
|
|
389
|
-
data?.pending !== void 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
390
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Ch\u1EDD x\u1EED l\xFD: " }),
|
|
391
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium text-amber-600 dark:text-amber-400", children: data.pending })
|
|
392
|
-
] }),
|
|
393
|
-
data?.completed !== void 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
394
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Ho\xE0n th\xE0nh: " }),
|
|
395
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium text-green-600 dark:text-green-400", children: data.completed })
|
|
396
|
-
] })
|
|
397
|
-
] })
|
|
398
|
-
] }),
|
|
399
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-blue-100 p-3 dark:bg-blue-900/30", children: /* @__PURE__ */ jsx(ShoppingCart, { className: "h-6 w-6 text-blue-600 dark:text-blue-400" }) })
|
|
400
|
-
] })
|
|
401
|
-
}
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
function CustomersWidget({
|
|
405
|
-
config,
|
|
406
|
-
data,
|
|
407
|
-
loading,
|
|
408
|
-
error,
|
|
409
|
-
onRemove,
|
|
410
|
-
onToggleVisibility,
|
|
411
|
-
onSettings
|
|
412
|
-
}) {
|
|
413
|
-
return /* @__PURE__ */ jsx(
|
|
414
|
-
BaseWidget,
|
|
415
|
-
{
|
|
416
|
-
config,
|
|
417
|
-
title: "Kh\xE1ch h\xE0ng",
|
|
418
|
-
loading,
|
|
419
|
-
error,
|
|
420
|
-
onRemove,
|
|
421
|
-
onToggleVisibility,
|
|
422
|
-
onSettings,
|
|
423
|
-
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
|
|
424
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
425
|
-
/* @__PURE__ */ jsx(
|
|
426
|
-
WidgetStat,
|
|
427
|
-
{
|
|
428
|
-
value: data?.total ?? 0,
|
|
429
|
-
label: "T\u1ED5ng kh\xE1ch h\xE0ng",
|
|
430
|
-
change: data?.change !== void 0 ? {
|
|
431
|
-
value: data.change,
|
|
432
|
-
type: data.change >= 0 ? "increase" : "decrease",
|
|
433
|
-
period: data.changePeriod || "th\xE1ng tr\u01B0\u1EDBc"
|
|
434
|
-
} : void 0,
|
|
435
|
-
valueClassName: "text-purple-600 dark:text-purple-400"
|
|
436
|
-
}
|
|
437
|
-
),
|
|
438
|
-
data?.newToday !== void 0 && data.newToday > 0 && /* @__PURE__ */ jsxs("div", { className: "text-sm", children: [
|
|
439
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Kh\xE1ch m\u1EDBi h\xF4m nay: " }),
|
|
440
|
-
/* @__PURE__ */ jsxs("span", { className: "font-medium text-green-600 dark:text-green-400", children: [
|
|
441
|
-
"+",
|
|
442
|
-
data.newToday
|
|
443
|
-
] })
|
|
444
|
-
] })
|
|
445
|
-
] }),
|
|
446
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-purple-100 p-3 dark:bg-purple-900/30", children: /* @__PURE__ */ jsx(Users, { className: "h-6 w-6 text-purple-600 dark:text-purple-400" }) })
|
|
447
|
-
] })
|
|
448
|
-
}
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
function StockWidget({
|
|
452
|
-
config,
|
|
453
|
-
data,
|
|
454
|
-
loading,
|
|
455
|
-
error,
|
|
456
|
-
onRemove,
|
|
457
|
-
onToggleVisibility,
|
|
458
|
-
onSettings
|
|
459
|
-
}) {
|
|
460
|
-
const hasLowStock = (data?.lowStockCount ?? 0) > 0;
|
|
461
|
-
return /* @__PURE__ */ jsx(
|
|
462
|
-
BaseWidget,
|
|
463
|
-
{
|
|
464
|
-
config,
|
|
465
|
-
title: "T\u1ED3n kho",
|
|
466
|
-
loading,
|
|
467
|
-
error,
|
|
468
|
-
onRemove,
|
|
469
|
-
onToggleVisibility,
|
|
470
|
-
onSettings,
|
|
471
|
-
children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
472
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
|
|
473
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
474
|
-
/* @__PURE__ */ jsx("div", { className: "text-3xl font-bold text-foreground", children: data?.totalItems ?? 0 }),
|
|
475
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "T\u1ED5ng m\u1EB7t h\xE0ng" })
|
|
476
|
-
] }),
|
|
477
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-orange-100 p-3 dark:bg-orange-900/30", children: /* @__PURE__ */ jsx(Package, { className: "h-6 w-6 text-orange-600 dark:text-orange-400" }) })
|
|
478
|
-
] }),
|
|
479
|
-
hasLowStock && /* @__PURE__ */ jsxs(
|
|
480
|
-
"div",
|
|
481
|
-
{
|
|
482
|
-
className: cn(
|
|
483
|
-
"flex items-center gap-2 rounded-lg px-3 py-2",
|
|
484
|
-
"bg-amber-50 text-amber-800 dark:bg-amber-900/20 dark:text-amber-400"
|
|
485
|
-
),
|
|
486
|
-
children: [
|
|
487
|
-
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-4 w-4 flex-shrink-0" }),
|
|
488
|
-
/* @__PURE__ */ jsxs("span", { className: "text-sm font-medium", children: [
|
|
489
|
-
data?.lowStockCount,
|
|
490
|
-
" s\u1EA3n ph\u1EA9m s\u1EAFp h\u1EBFt h\xE0ng"
|
|
491
|
-
] })
|
|
492
|
-
]
|
|
493
|
-
}
|
|
494
|
-
),
|
|
495
|
-
data?.lowStockItems && data.lowStockItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
496
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider", children: "C\u1EA3nh b\xE1o t\u1ED3n kho" }),
|
|
497
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5 max-h-32 overflow-y-auto", children: [
|
|
498
|
-
data.lowStockItems.slice(0, 5).map((item) => /* @__PURE__ */ jsxs(
|
|
499
|
-
"div",
|
|
500
|
-
{
|
|
501
|
-
className: "flex items-center justify-between text-sm",
|
|
502
|
-
children: [
|
|
503
|
-
/* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: item.name }),
|
|
504
|
-
/* @__PURE__ */ jsxs("span", { className: "flex-shrink-0 font-medium text-red-600 dark:text-red-400", children: [
|
|
505
|
-
item.quantity,
|
|
506
|
-
"/",
|
|
507
|
-
item.minStock
|
|
508
|
-
] })
|
|
509
|
-
]
|
|
510
|
-
},
|
|
511
|
-
item.id
|
|
512
|
-
)),
|
|
513
|
-
data.lowStockItems.length > 5 && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
514
|
-
"+",
|
|
515
|
-
data.lowStockItems.length - 5,
|
|
516
|
-
" s\u1EA3n ph\u1EA9m kh\xE1c"
|
|
517
|
-
] })
|
|
518
|
-
] })
|
|
519
|
-
] })
|
|
520
|
-
] })
|
|
521
|
-
}
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
function WidgetContainer({
|
|
525
|
-
widgets,
|
|
526
|
-
onWidgetsChange,
|
|
527
|
-
widgetData,
|
|
528
|
-
loading = false,
|
|
529
|
-
className
|
|
530
|
-
}) {
|
|
531
|
-
const [showHiddenWidgets, setShowHiddenWidgets] = useState(false);
|
|
532
|
-
const sensors = useSensors(
|
|
533
|
-
useSensor(PointerSensor, {
|
|
534
|
-
activationConstraint: {
|
|
535
|
-
distance: 8
|
|
536
|
-
}
|
|
537
|
-
}),
|
|
538
|
-
useSensor(KeyboardSensor, {
|
|
539
|
-
coordinateGetter: sortableKeyboardCoordinates
|
|
540
|
-
})
|
|
541
|
-
);
|
|
542
|
-
const handleDragEnd = useCallback(
|
|
543
|
-
(event) => {
|
|
544
|
-
const { active, over } = event;
|
|
545
|
-
if (over && active.id !== over.id) {
|
|
546
|
-
const oldIndex = widgets.findIndex((w) => w.id === active.id);
|
|
547
|
-
const newIndex = widgets.findIndex((w) => w.id === over.id);
|
|
548
|
-
const newWidgets = arrayMove(widgets, oldIndex, newIndex).map(
|
|
549
|
-
(widget, index) => ({
|
|
550
|
-
...widget,
|
|
551
|
-
position: index
|
|
552
|
-
})
|
|
553
|
-
);
|
|
554
|
-
onWidgetsChange(newWidgets);
|
|
555
|
-
}
|
|
556
|
-
},
|
|
557
|
-
[widgets, onWidgetsChange]
|
|
558
|
-
);
|
|
559
|
-
const handleToggleVisibility = useCallback(
|
|
560
|
-
(widgetId) => {
|
|
561
|
-
const newWidgets = widgets.map(
|
|
562
|
-
(widget) => widget.id === widgetId ? { ...widget, visible: !widget.visible } : widget
|
|
563
|
-
);
|
|
564
|
-
onWidgetsChange(newWidgets);
|
|
565
|
-
},
|
|
566
|
-
[widgets, onWidgetsChange]
|
|
567
|
-
);
|
|
568
|
-
const handleRemoveWidget = useCallback(
|
|
569
|
-
(widgetId) => {
|
|
570
|
-
const newWidgets = widgets.map(
|
|
571
|
-
(widget) => widget.id === widgetId ? { ...widget, visible: false } : widget
|
|
572
|
-
);
|
|
573
|
-
onWidgetsChange(newWidgets);
|
|
574
|
-
},
|
|
575
|
-
[widgets, onWidgetsChange]
|
|
576
|
-
);
|
|
577
|
-
const handleResetWidgets = useCallback(() => {
|
|
578
|
-
onWidgetsChange(DEFAULT_WIDGETS);
|
|
579
|
-
}, [onWidgetsChange]);
|
|
580
|
-
const visibleWidgets = widgets.filter((w) => w.visible).sort((a, b) => a.position - b.position);
|
|
581
|
-
const hiddenWidgets = widgets.filter((w) => !w.visible);
|
|
582
|
-
const renderWidget = (config) => {
|
|
583
|
-
const commonProps = {
|
|
584
|
-
config,
|
|
585
|
-
loading,
|
|
586
|
-
onRemove: () => handleRemoveWidget(config.id),
|
|
587
|
-
onToggleVisibility: () => handleToggleVisibility(config.id)
|
|
588
|
-
};
|
|
589
|
-
switch (config.type) {
|
|
590
|
-
case "revenue":
|
|
591
|
-
return /* @__PURE__ */ jsx(RevenueWidget, { ...commonProps, data: widgetData?.revenue });
|
|
592
|
-
case "orders":
|
|
593
|
-
return /* @__PURE__ */ jsx(OrdersWidget, { ...commonProps, data: widgetData?.orders });
|
|
594
|
-
case "customers":
|
|
595
|
-
return /* @__PURE__ */ jsx(CustomersWidget, { ...commonProps, data: widgetData?.customers });
|
|
596
|
-
case "stock":
|
|
597
|
-
return /* @__PURE__ */ jsx(StockWidget, { ...commonProps, data: widgetData?.stock });
|
|
598
|
-
default:
|
|
599
|
-
return null;
|
|
600
|
-
}
|
|
601
|
-
};
|
|
602
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), children: [
|
|
603
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
604
|
-
/* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-foreground", children: "Widget Dashboard" }),
|
|
605
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
606
|
-
hiddenWidgets.length > 0 && /* @__PURE__ */ jsxs(
|
|
607
|
-
"button",
|
|
608
|
-
{
|
|
609
|
-
onClick: () => setShowHiddenWidgets(!showHiddenWidgets),
|
|
610
|
-
className: "inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground transition-colors",
|
|
611
|
-
children: [
|
|
612
|
-
showHiddenWidgets ? /* @__PURE__ */ jsx(EyeOff, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Eye, { className: "h-4 w-4" }),
|
|
613
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
614
|
-
hiddenWidgets.length,
|
|
615
|
-
" \u1EA9n"
|
|
616
|
-
] })
|
|
617
|
-
]
|
|
618
|
-
}
|
|
619
|
-
),
|
|
620
|
-
/* @__PURE__ */ jsx(
|
|
621
|
-
"button",
|
|
622
|
-
{
|
|
623
|
-
onClick: handleResetWidgets,
|
|
624
|
-
className: "inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground transition-colors",
|
|
625
|
-
title: "Kh\xF4i ph\u1EE5c m\u1EB7c \u0111\u1ECBnh",
|
|
626
|
-
children: /* @__PURE__ */ jsx(RotateCcw, { className: "h-4 w-4" })
|
|
627
|
-
}
|
|
628
|
-
)
|
|
629
|
-
] })
|
|
630
|
-
] }),
|
|
631
|
-
/* @__PURE__ */ jsx(AnimatePresence, { children: showHiddenWidgets && hiddenWidgets.length > 0 && /* @__PURE__ */ jsx(
|
|
632
|
-
motion.div,
|
|
633
|
-
{
|
|
634
|
-
initial: { opacity: 0, height: 0 },
|
|
635
|
-
animate: { opacity: 1, height: "auto" },
|
|
636
|
-
exit: { opacity: 0, height: 0 },
|
|
637
|
-
className: "overflow-hidden",
|
|
638
|
-
children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed border-border bg-muted/30 p-4", children: [
|
|
639
|
-
/* @__PURE__ */ jsx("p", { className: "mb-3 text-sm font-medium text-muted-foreground", children: "Widget \u0111\xE3 \u1EA9n - Click \u0111\u1EC3 hi\u1EC3n th\u1ECB l\u1EA1i" }),
|
|
640
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: hiddenWidgets.map((widget) => /* @__PURE__ */ jsxs(
|
|
641
|
-
"button",
|
|
642
|
-
{
|
|
643
|
-
onClick: () => handleToggleVisibility(widget.id),
|
|
644
|
-
className: "inline-flex items-center gap-1.5 rounded-lg bg-background px-3 py-2 text-sm font-medium shadow-sm border border-border hover:bg-muted transition-colors",
|
|
645
|
-
children: [
|
|
646
|
-
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
|
|
647
|
-
WIDGET_LABELS[widget.type]
|
|
648
|
-
]
|
|
649
|
-
},
|
|
650
|
-
widget.id
|
|
651
|
-
)) })
|
|
652
|
-
] })
|
|
653
|
-
}
|
|
654
|
-
) }),
|
|
655
|
-
/* @__PURE__ */ jsx(
|
|
656
|
-
DndContext,
|
|
657
|
-
{
|
|
658
|
-
sensors,
|
|
659
|
-
collisionDetection: closestCenter,
|
|
660
|
-
onDragEnd: handleDragEnd,
|
|
661
|
-
children: /* @__PURE__ */ jsx(
|
|
662
|
-
SortableContext,
|
|
663
|
-
{
|
|
664
|
-
items: visibleWidgets.map((w) => w.id),
|
|
665
|
-
strategy: rectSortingStrategy,
|
|
666
|
-
children: /* @__PURE__ */ jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4", children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", children: visibleWidgets.map((widget) => /* @__PURE__ */ jsx("div", { children: renderWidget(widget) }, widget.id)) }) })
|
|
667
|
-
}
|
|
668
|
-
)
|
|
669
|
-
}
|
|
670
|
-
),
|
|
671
|
-
visibleWidgets.length === 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center rounded-lg border border-dashed border-border py-12 text-center", children: [
|
|
672
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-muted p-3 mb-4", children: /* @__PURE__ */ jsx(Plus, { className: "h-6 w-6 text-muted-foreground" }) }),
|
|
673
|
-
/* @__PURE__ */ jsx("h3", { className: "font-medium text-foreground mb-1", children: "Ch\u01B0a c\xF3 widget n\xE0o" }),
|
|
674
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: "Th\xEAm widget \u0111\u1EC3 theo d\xF5i c\xE1c ch\u1EC9 s\u1ED1 quan tr\u1ECDng" }),
|
|
675
|
-
/* @__PURE__ */ jsxs(
|
|
676
|
-
"button",
|
|
677
|
-
{
|
|
678
|
-
onClick: handleResetWidgets,
|
|
679
|
-
className: "inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors",
|
|
680
|
-
children: [
|
|
681
|
-
/* @__PURE__ */ jsx(RotateCcw, { className: "h-4 w-4" }),
|
|
682
|
-
"Kh\xF4i ph\u1EE5c m\u1EB7c \u0111\u1ECBnh"
|
|
683
|
-
]
|
|
684
|
-
}
|
|
685
|
-
)
|
|
686
|
-
] })
|
|
687
|
-
] });
|
|
688
|
-
}
|
|
689
|
-
function FeatureShowcase({ className }) {
|
|
690
|
-
const [emblaRef, emblaApi] = useEmblaCarousel(
|
|
691
|
-
{ loop: true, align: "start" },
|
|
692
|
-
[Autoplay({ delay: 5e3, stopOnInteraction: true })]
|
|
693
|
-
);
|
|
694
|
-
const [prevBtnEnabled, setPrevBtnEnabled] = useState(false);
|
|
695
|
-
const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
|
|
696
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
697
|
-
const [scrollSnaps, setScrollSnaps] = useState([]);
|
|
698
|
-
const scrollPrev = useCallback(
|
|
699
|
-
() => emblaApi && emblaApi.scrollPrev(),
|
|
700
|
-
[emblaApi]
|
|
701
|
-
);
|
|
702
|
-
const scrollNext = useCallback(
|
|
703
|
-
() => emblaApi && emblaApi.scrollNext(),
|
|
704
|
-
[emblaApi]
|
|
705
|
-
);
|
|
706
|
-
const scrollTo = useCallback(
|
|
707
|
-
(index) => emblaApi && emblaApi.scrollTo(index),
|
|
708
|
-
[emblaApi]
|
|
709
|
-
);
|
|
710
|
-
const onSelect = useCallback(() => {
|
|
711
|
-
if (!emblaApi) return;
|
|
712
|
-
setSelectedIndex(emblaApi.selectedScrollSnap());
|
|
713
|
-
setPrevBtnEnabled(emblaApi.canScrollPrev());
|
|
714
|
-
setNextBtnEnabled(emblaApi.canScrollNext());
|
|
715
|
-
}, [emblaApi]);
|
|
716
|
-
useEffect(() => {
|
|
717
|
-
if (!emblaApi) return;
|
|
718
|
-
onSelect();
|
|
719
|
-
setScrollSnaps(emblaApi.scrollSnapList());
|
|
720
|
-
emblaApi.on("select", onSelect);
|
|
721
|
-
emblaApi.on("reInit", onSelect);
|
|
722
|
-
return () => {
|
|
723
|
-
emblaApi.off("select", onSelect);
|
|
724
|
-
emblaApi.off("reInit", onSelect);
|
|
725
|
-
};
|
|
726
|
-
}, [emblaApi, onSelect]);
|
|
727
|
-
return /* @__PURE__ */ jsxs(
|
|
728
|
-
"div",
|
|
729
|
-
{
|
|
730
|
-
className: cn(
|
|
731
|
-
"bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 shadow-sm relative overflow-hidden flex flex-col h-full",
|
|
732
|
-
className
|
|
733
|
-
),
|
|
734
|
-
children: [
|
|
735
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-b border-slate-100 dark:border-slate-700/50 bg-white/50 dark:bg-slate-800/50 backdrop-blur-sm z-20", children: [
|
|
736
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
737
|
-
/* @__PURE__ */ jsx("div", { className: "bg-blue-100 dark:bg-blue-900/30 text-primary p-1 rounded-md", children: /* @__PURE__ */ jsx(
|
|
738
|
-
"svg",
|
|
739
|
-
{
|
|
740
|
-
className: "w-[18px] h-[18px]",
|
|
741
|
-
viewBox: "0 0 24 24",
|
|
742
|
-
fill: "none",
|
|
743
|
-
stroke: "currentColor",
|
|
744
|
-
strokeWidth: "2",
|
|
745
|
-
children: /* @__PURE__ */ jsx("path", { d: "M12 2v20M2 12h20M12 2a10 10 0 0 1 10 10 10 10 0 0 1-10 10 10 10 0 0 1-10-10 10 10 0 0 1 10-10z" })
|
|
746
|
-
}
|
|
747
|
-
) }),
|
|
748
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-bold text-slate-700 dark:text-slate-200", children: "Feature Tour" })
|
|
749
|
-
] }),
|
|
750
|
-
/* @__PURE__ */ jsx("button", { className: "text-xs font-medium text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-200 px-3 py-1.5 rounded hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors", children: "Skip Tour" })
|
|
751
|
-
] }),
|
|
752
|
-
/* @__PURE__ */ jsx(
|
|
753
|
-
"div",
|
|
754
|
-
{
|
|
755
|
-
className: "flex-1 relative overflow-hidden bg-slate-50/50 dark:bg-slate-800/50",
|
|
756
|
-
ref: emblaRef,
|
|
757
|
-
children: /* @__PURE__ */ jsx("div", { className: "flex touch-pan-y h-full", children: ERP_FEATURES.map((feature, index) => /* @__PURE__ */ jsx("div", { className: "flex-[0_0_100%] min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "p-6 sm:p-8 flex flex-col items-center text-center gap-6 h-full justify-center", children: [
|
|
758
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-2 text-xs font-semibold text-primary uppercase tracking-wider", children: [
|
|
759
|
-
"Step ",
|
|
760
|
-
index + 1,
|
|
761
|
-
" of ",
|
|
762
|
-
ERP_FEATURES.length
|
|
763
|
-
] }),
|
|
764
|
-
/* @__PURE__ */ jsx(
|
|
765
|
-
"div",
|
|
766
|
-
{
|
|
767
|
-
className: cn(
|
|
768
|
-
"flex h-16 w-16 shrink-0 items-center justify-center rounded-2xl bg-gradient-to-br text-3xl shadow-md",
|
|
769
|
-
feature.color
|
|
770
|
-
),
|
|
771
|
-
children: feature.icon
|
|
772
|
-
}
|
|
773
|
-
),
|
|
774
|
-
/* @__PURE__ */ jsx("h2", { className: "text-2xl md:text-3xl font-bold text-slate-900 dark:text-white", children: feature.title }),
|
|
775
|
-
/* @__PURE__ */ jsx("p", { className: "text-slate-500 dark:text-slate-400 text-sm md:text-base leading-relaxed max-w-md", children: feature.description }),
|
|
776
|
-
/* @__PURE__ */ jsx("div", { className: "flex gap-2 justify-center flex-wrap", children: feature.subFeatures.map((sub) => /* @__PURE__ */ jsx(
|
|
777
|
-
"span",
|
|
778
|
-
{
|
|
779
|
-
className: "px-2 py-1 bg-white dark:bg-slate-700 border border-slate-200 dark:border-slate-600 rounded-md text-xs font-medium text-slate-600 dark:text-slate-300",
|
|
780
|
-
children: sub
|
|
781
|
-
},
|
|
782
|
-
sub
|
|
783
|
-
)) })
|
|
784
|
-
] }) }, feature.id)) })
|
|
785
|
-
}
|
|
786
|
-
),
|
|
787
|
-
/* @__PURE__ */ jsxs("div", { className: "px-6 py-4 bg-white dark:bg-slate-800 border-t border-slate-100 dark:border-slate-700 flex items-center justify-between z-20", children: [
|
|
788
|
-
/* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: scrollSnaps.map((_, index) => /* @__PURE__ */ jsx(
|
|
789
|
-
"button",
|
|
790
|
-
{
|
|
791
|
-
className: cn(
|
|
792
|
-
"h-1.5 rounded-full transition-all duration-300",
|
|
793
|
-
index === selectedIndex ? "w-6 bg-primary" : "w-1.5 bg-slate-300 dark:bg-slate-600"
|
|
794
|
-
),
|
|
795
|
-
onClick: () => scrollTo(index)
|
|
796
|
-
},
|
|
797
|
-
index
|
|
798
|
-
)) }),
|
|
799
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
|
|
800
|
-
/* @__PURE__ */ jsx(
|
|
801
|
-
"button",
|
|
802
|
-
{
|
|
803
|
-
onClick: scrollPrev,
|
|
804
|
-
disabled: !prevBtnEnabled,
|
|
805
|
-
className: "w-9 h-9 flex items-center justify-center rounded-lg border border-slate-200 dark:border-slate-700 text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",
|
|
806
|
-
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "w-4 h-4" })
|
|
807
|
-
}
|
|
808
|
-
),
|
|
809
|
-
/* @__PURE__ */ jsxs(
|
|
810
|
-
"button",
|
|
811
|
-
{
|
|
812
|
-
onClick: scrollNext,
|
|
813
|
-
className: "h-9 px-4 rounded-lg bg-primary text-white text-sm font-medium flex items-center gap-2 hover:bg-primary/90 transition-colors shadow-sm",
|
|
814
|
-
children: [
|
|
815
|
-
"Next",
|
|
816
|
-
/* @__PURE__ */ jsx(ChevronRight, { className: "w-4 h-4" })
|
|
817
|
-
]
|
|
818
|
-
}
|
|
819
|
-
)
|
|
820
|
-
] })
|
|
821
|
-
] })
|
|
822
|
-
]
|
|
823
|
-
}
|
|
824
|
-
);
|
|
825
|
-
}
|
|
826
|
-
var iconMap = {
|
|
827
|
-
"shopping-cart": /* @__PURE__ */ jsx(
|
|
828
|
-
"svg",
|
|
829
|
-
{
|
|
830
|
-
className: "h-5 w-5",
|
|
831
|
-
fill: "none",
|
|
832
|
-
stroke: "currentColor",
|
|
833
|
-
viewBox: "0 0 24 24",
|
|
834
|
-
children: /* @__PURE__ */ jsx(
|
|
835
|
-
"path",
|
|
836
|
-
{
|
|
837
|
-
strokeLinecap: "round",
|
|
838
|
-
strokeLinejoin: "round",
|
|
839
|
-
strokeWidth: 2,
|
|
840
|
-
d: "M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"
|
|
841
|
-
}
|
|
842
|
-
)
|
|
843
|
-
}
|
|
844
|
-
),
|
|
845
|
-
users: /* @__PURE__ */ jsx(
|
|
846
|
-
"svg",
|
|
847
|
-
{
|
|
848
|
-
className: "h-5 w-5",
|
|
849
|
-
fill: "none",
|
|
850
|
-
stroke: "currentColor",
|
|
851
|
-
viewBox: "0 0 24 24",
|
|
852
|
-
children: /* @__PURE__ */ jsx(
|
|
853
|
-
"path",
|
|
854
|
-
{
|
|
855
|
-
strokeLinecap: "round",
|
|
856
|
-
strokeLinejoin: "round",
|
|
857
|
-
strokeWidth: 2,
|
|
858
|
-
d: "M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
|
|
859
|
-
}
|
|
860
|
-
)
|
|
861
|
-
}
|
|
862
|
-
),
|
|
863
|
-
package: /* @__PURE__ */ jsx(
|
|
864
|
-
"svg",
|
|
865
|
-
{
|
|
866
|
-
className: "h-5 w-5",
|
|
867
|
-
fill: "none",
|
|
868
|
-
stroke: "currentColor",
|
|
869
|
-
viewBox: "0 0 24 24",
|
|
870
|
-
children: /* @__PURE__ */ jsx(
|
|
871
|
-
"path",
|
|
872
|
-
{
|
|
873
|
-
strokeLinecap: "round",
|
|
874
|
-
strokeLinejoin: "round",
|
|
875
|
-
strokeWidth: 2,
|
|
876
|
-
d: "M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"
|
|
877
|
-
}
|
|
878
|
-
)
|
|
879
|
-
}
|
|
880
|
-
),
|
|
881
|
-
"chart-bar": /* @__PURE__ */ jsx(
|
|
882
|
-
"svg",
|
|
883
|
-
{
|
|
884
|
-
className: "h-5 w-5",
|
|
885
|
-
fill: "none",
|
|
886
|
-
stroke: "currentColor",
|
|
887
|
-
viewBox: "0 0 24 24",
|
|
888
|
-
children: /* @__PURE__ */ jsx(
|
|
889
|
-
"path",
|
|
890
|
-
{
|
|
891
|
-
strokeLinecap: "round",
|
|
892
|
-
strokeLinejoin: "round",
|
|
893
|
-
strokeWidth: 2,
|
|
894
|
-
d: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
|
895
|
-
}
|
|
896
|
-
)
|
|
897
|
-
}
|
|
898
|
-
),
|
|
899
|
-
"document-text": /* @__PURE__ */ jsx(
|
|
900
|
-
"svg",
|
|
901
|
-
{
|
|
902
|
-
className: "h-5 w-5",
|
|
903
|
-
fill: "none",
|
|
904
|
-
stroke: "currentColor",
|
|
905
|
-
viewBox: "0 0 24 24",
|
|
906
|
-
children: /* @__PURE__ */ jsx(
|
|
907
|
-
"path",
|
|
908
|
-
{
|
|
909
|
-
strokeLinecap: "round",
|
|
910
|
-
strokeLinejoin: "round",
|
|
911
|
-
strokeWidth: 2,
|
|
912
|
-
d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
|
913
|
-
}
|
|
914
|
-
)
|
|
915
|
-
}
|
|
916
|
-
),
|
|
917
|
-
cog: /* @__PURE__ */ jsxs(
|
|
918
|
-
"svg",
|
|
919
|
-
{
|
|
920
|
-
className: "h-5 w-5",
|
|
921
|
-
fill: "none",
|
|
922
|
-
stroke: "currentColor",
|
|
923
|
-
viewBox: "0 0 24 24",
|
|
924
|
-
children: [
|
|
925
|
-
/* @__PURE__ */ jsx(
|
|
926
|
-
"path",
|
|
927
|
-
{
|
|
928
|
-
strokeLinecap: "round",
|
|
929
|
-
strokeLinejoin: "round",
|
|
930
|
-
strokeWidth: 2,
|
|
931
|
-
d: "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
|
932
|
-
}
|
|
933
|
-
),
|
|
934
|
-
/* @__PURE__ */ jsx(
|
|
935
|
-
"path",
|
|
936
|
-
{
|
|
937
|
-
strokeLinecap: "round",
|
|
938
|
-
strokeLinejoin: "round",
|
|
939
|
-
strokeWidth: 2,
|
|
940
|
-
d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
941
|
-
}
|
|
942
|
-
)
|
|
943
|
-
]
|
|
944
|
-
}
|
|
945
|
-
),
|
|
946
|
-
"currency-dollar": /* @__PURE__ */ jsx(
|
|
947
|
-
"svg",
|
|
948
|
-
{
|
|
949
|
-
className: "h-5 w-5",
|
|
950
|
-
fill: "none",
|
|
951
|
-
stroke: "currentColor",
|
|
952
|
-
viewBox: "0 0 24 24",
|
|
953
|
-
children: /* @__PURE__ */ jsx(
|
|
954
|
-
"path",
|
|
955
|
-
{
|
|
956
|
-
strokeLinecap: "round",
|
|
957
|
-
strokeLinejoin: "round",
|
|
958
|
-
strokeWidth: 2,
|
|
959
|
-
d: "M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
960
|
-
}
|
|
961
|
-
)
|
|
962
|
-
}
|
|
963
|
-
),
|
|
964
|
-
"clipboard-list": /* @__PURE__ */ jsx(
|
|
965
|
-
"svg",
|
|
966
|
-
{
|
|
967
|
-
className: "h-5 w-5",
|
|
968
|
-
fill: "none",
|
|
969
|
-
stroke: "currentColor",
|
|
970
|
-
viewBox: "0 0 24 24",
|
|
971
|
-
children: /* @__PURE__ */ jsx(
|
|
972
|
-
"path",
|
|
973
|
-
{
|
|
974
|
-
strokeLinecap: "round",
|
|
975
|
-
strokeLinejoin: "round",
|
|
976
|
-
strokeWidth: 2,
|
|
977
|
-
d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"
|
|
978
|
-
}
|
|
979
|
-
)
|
|
980
|
-
}
|
|
981
|
-
)
|
|
982
|
-
};
|
|
983
|
-
var colorClasses = {
|
|
984
|
-
blue: "bg-blue-500 hover:bg-blue-600 text-white",
|
|
985
|
-
green: "bg-green-500 hover:bg-green-600 text-white",
|
|
986
|
-
purple: "bg-purple-500 hover:bg-purple-600 text-white",
|
|
987
|
-
orange: "bg-orange-500 hover:bg-orange-600 text-white",
|
|
988
|
-
pink: "bg-pink-500 hover:bg-pink-600 text-white",
|
|
989
|
-
indigo: "bg-indigo-500 hover:bg-indigo-600 text-white",
|
|
990
|
-
teal: "bg-teal-500 hover:bg-teal-600 text-white",
|
|
991
|
-
red: "bg-red-500 hover:bg-red-600 text-white"
|
|
992
|
-
};
|
|
993
|
-
var AVAILABLE_ICONS = Object.keys(iconMap);
|
|
994
|
-
var AVAILABLE_COLORS = [
|
|
995
|
-
"blue",
|
|
996
|
-
"green",
|
|
997
|
-
"purple",
|
|
998
|
-
"orange",
|
|
999
|
-
"pink",
|
|
1000
|
-
"indigo",
|
|
1001
|
-
"teal",
|
|
1002
|
-
"red"
|
|
1003
|
-
];
|
|
1004
|
-
var formSchema = z.object({
|
|
1005
|
-
label: z.string().min(1, "Vui l\xF2ng nh\u1EADp t\xEAn l\u1ED1i t\u1EAFt"),
|
|
1006
|
-
href: z.string().min(1, "Vui l\xF2ng nh\u1EADp \u0111\u01B0\u1EDDng d\u1EABn"),
|
|
1007
|
-
icon: z.string(),
|
|
1008
|
-
color: z.string()
|
|
1009
|
-
});
|
|
1010
|
-
function QuickAccessDialog({
|
|
1011
|
-
open,
|
|
1012
|
-
onOpenChange,
|
|
1013
|
-
onSubmit,
|
|
1014
|
-
initialData,
|
|
1015
|
-
availableFeatures
|
|
1016
|
-
}) {
|
|
1017
|
-
const form = useForm({
|
|
1018
|
-
resolver: zodResolver(formSchema),
|
|
1019
|
-
defaultValues: {
|
|
1020
|
-
label: "",
|
|
1021
|
-
href: "",
|
|
1022
|
-
icon: "document-text",
|
|
1023
|
-
color: "blue"
|
|
1024
|
-
}
|
|
1025
|
-
});
|
|
1026
|
-
useEffect(() => {
|
|
1027
|
-
if (open) {
|
|
1028
|
-
form.reset(
|
|
1029
|
-
initialData ? {
|
|
1030
|
-
label: initialData.label,
|
|
1031
|
-
href: initialData.href,
|
|
1032
|
-
icon: initialData.icon,
|
|
1033
|
-
color: initialData.color
|
|
1034
|
-
} : {
|
|
1035
|
-
label: "",
|
|
1036
|
-
href: "",
|
|
1037
|
-
icon: "document-text",
|
|
1038
|
-
color: "blue"
|
|
1039
|
-
}
|
|
1040
|
-
);
|
|
1041
|
-
}
|
|
1042
|
-
}, [open, initialData, form]);
|
|
1043
|
-
const handleSubmit = (values) => {
|
|
1044
|
-
onSubmit(values);
|
|
1045
|
-
onOpenChange(false);
|
|
1046
|
-
};
|
|
1047
|
-
const handleFeatureChange = (e) => {
|
|
1048
|
-
const selectedHref = e.target.value;
|
|
1049
|
-
const feature = availableFeatures?.find((f) => f.href === selectedHref);
|
|
1050
|
-
if (feature) {
|
|
1051
|
-
form.setValue("href", feature.href);
|
|
1052
|
-
form.setValue("label", feature.label);
|
|
1053
|
-
if (feature.icon && iconMap[feature.icon]) {
|
|
1054
|
-
form.setValue("icon", feature.icon);
|
|
1055
|
-
} else if (feature.icon && iconMap[feature.icon.replace(/-/g, "")]) ;
|
|
1056
|
-
}
|
|
1057
|
-
};
|
|
1058
|
-
return /* @__PURE__ */ jsx(DialogPrimitive.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [
|
|
1059
|
-
/* @__PURE__ */ jsx(DialogPrimitive.Overlay, { className: "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" }),
|
|
1060
|
-
/* @__PURE__ */ jsxs(DialogPrimitive.Content, { className: "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", children: [
|
|
1061
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col space-y-1.5 text-center sm:text-left", children: [
|
|
1062
|
-
/* @__PURE__ */ jsx(DialogPrimitive.Title, { className: "text-lg font-semibold leading-none tracking-tight", children: initialData ? "S\u1EEDa l\u1ED1i t\u1EAFt" : "Th\xEAm l\u1ED1i t\u1EAFt m\u1EDBi" }),
|
|
1063
|
-
/* @__PURE__ */ jsx(DialogPrimitive.Description, { className: "text-sm text-muted-foreground", children: "T\u1EA1o l\u1ED1i t\u1EAFt \u0111\u1EC3 truy c\u1EADp nhanh c\xE1c t\xEDnh n\u0103ng th\u01B0\u1EDDng d\xF9ng" })
|
|
1064
|
-
] }),
|
|
1065
|
-
/* @__PURE__ */ jsxs(
|
|
1066
|
-
"form",
|
|
1067
|
-
{
|
|
1068
|
-
onSubmit: form.handleSubmit(handleSubmit),
|
|
1069
|
-
className: "space-y-4",
|
|
1070
|
-
children: [
|
|
1071
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-4 py-4", children: [
|
|
1072
|
-
availableFeatures && availableFeatures.length > 0 && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-4 items-center gap-4", children: [
|
|
1073
|
-
/* @__PURE__ */ jsx(
|
|
1074
|
-
"label",
|
|
1075
|
-
{
|
|
1076
|
-
htmlFor: "feature-select",
|
|
1077
|
-
className: "text-right text-sm font-medium",
|
|
1078
|
-
children: "Ch\u1EE9c n\u0103ng"
|
|
1079
|
-
}
|
|
1080
|
-
),
|
|
1081
|
-
/* @__PURE__ */ jsx("div", { className: "col-span-3", children: /* @__PURE__ */ jsxs(
|
|
1082
|
-
"select",
|
|
1083
|
-
{
|
|
1084
|
-
id: "feature-select",
|
|
1085
|
-
className: "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
1086
|
-
onChange: handleFeatureChange,
|
|
1087
|
-
defaultValue: initialData?.href || "",
|
|
1088
|
-
children: [
|
|
1089
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "Ch\u1ECDn ch\u1EE9c n\u0103ng..." }),
|
|
1090
|
-
availableFeatures.map((f) => /* @__PURE__ */ jsx("option", { value: f.href, children: f.label }, f.href))
|
|
1091
|
-
]
|
|
1092
|
-
}
|
|
1093
|
-
) })
|
|
1094
|
-
] }),
|
|
1095
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-4 items-center gap-4", children: [
|
|
1096
|
-
/* @__PURE__ */ jsx(
|
|
1097
|
-
"label",
|
|
1098
|
-
{
|
|
1099
|
-
htmlFor: "label",
|
|
1100
|
-
className: "text-right text-sm font-medium",
|
|
1101
|
-
children: "T\xEAn l\u1ED1i t\u1EAFt"
|
|
1102
|
-
}
|
|
1103
|
-
),
|
|
1104
|
-
/* @__PURE__ */ jsxs("div", { className: "col-span-3", children: [
|
|
1105
|
-
/* @__PURE__ */ jsx(
|
|
1106
|
-
"input",
|
|
1107
|
-
{
|
|
1108
|
-
id: "label",
|
|
1109
|
-
className: "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
1110
|
-
...form.register("label")
|
|
1111
|
-
}
|
|
1112
|
-
),
|
|
1113
|
-
form.formState.errors.label && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-500 mt-1", children: form.formState.errors.label.message })
|
|
1114
|
-
] })
|
|
1115
|
-
] }),
|
|
1116
|
-
/* @__PURE__ */ jsxs(
|
|
1117
|
-
"div",
|
|
1118
|
-
{
|
|
1119
|
-
className: cn(
|
|
1120
|
-
"grid grid-cols-4 items-center gap-4",
|
|
1121
|
-
availableFeatures?.length ? "hidden" : ""
|
|
1122
|
-
),
|
|
1123
|
-
children: [
|
|
1124
|
-
/* @__PURE__ */ jsx(
|
|
1125
|
-
"label",
|
|
1126
|
-
{
|
|
1127
|
-
htmlFor: "href",
|
|
1128
|
-
className: "text-right text-sm font-medium",
|
|
1129
|
-
children: "\u0110\u01B0\u1EDDng d\u1EABn"
|
|
1130
|
-
}
|
|
1131
|
-
),
|
|
1132
|
-
/* @__PURE__ */ jsxs("div", { className: "col-span-3", children: [
|
|
1133
|
-
/* @__PURE__ */ jsx(
|
|
1134
|
-
"input",
|
|
1135
|
-
{
|
|
1136
|
-
id: "href",
|
|
1137
|
-
className: "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
1138
|
-
...form.register("href"),
|
|
1139
|
-
readOnly: !!availableFeatures?.length
|
|
1140
|
-
}
|
|
1141
|
-
),
|
|
1142
|
-
form.formState.errors.href && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-500 mt-1", children: form.formState.errors.href.message })
|
|
1143
|
-
] })
|
|
1144
|
-
]
|
|
1145
|
-
}
|
|
1146
|
-
),
|
|
1147
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-4 items-start gap-4", children: [
|
|
1148
|
-
/* @__PURE__ */ jsx("label", { className: "text-right text-sm font-medium pt-2", children: "Icon" }),
|
|
1149
|
-
/* @__PURE__ */ jsx("div", { className: "col-span-3 flex flex-wrap gap-2", children: AVAILABLE_ICONS.map((icon) => /* @__PURE__ */ jsx(
|
|
1150
|
-
"button",
|
|
1151
|
-
{
|
|
1152
|
-
type: "button",
|
|
1153
|
-
onClick: () => form.setValue("icon", icon),
|
|
1154
|
-
className: cn(
|
|
1155
|
-
"flex h-8 w-8 items-center justify-center rounded-md border transition-colors",
|
|
1156
|
-
form.watch("icon") === icon ? "border-primary bg-primary/10 text-primary" : "border-input hover:bg-muted"
|
|
1157
|
-
),
|
|
1158
|
-
children: iconMap[icon]
|
|
1159
|
-
},
|
|
1160
|
-
icon
|
|
1161
|
-
)) })
|
|
1162
|
-
] }),
|
|
1163
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-4 items-start gap-4", children: [
|
|
1164
|
-
/* @__PURE__ */ jsx("label", { className: "text-right text-sm font-medium pt-2", children: "M\xE0u s\u1EAFc" }),
|
|
1165
|
-
/* @__PURE__ */ jsx("div", { className: "col-span-3 flex flex-wrap gap-2", children: AVAILABLE_COLORS.map((color) => /* @__PURE__ */ jsx(
|
|
1166
|
-
"button",
|
|
1167
|
-
{
|
|
1168
|
-
type: "button",
|
|
1169
|
-
onClick: () => form.setValue("color", color),
|
|
1170
|
-
className: cn(
|
|
1171
|
-
"h-8 w-8 rounded-full border-2 transition-all",
|
|
1172
|
-
`bg-${color}-500`,
|
|
1173
|
-
form.watch("color") === color ? "border-primary scale-110 shadow-sm" : "border-transparent hover:scale-105"
|
|
1174
|
-
),
|
|
1175
|
-
"aria-label": color
|
|
1176
|
-
},
|
|
1177
|
-
color
|
|
1178
|
-
)) })
|
|
1179
|
-
] })
|
|
1180
|
-
] }),
|
|
1181
|
-
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
1182
|
-
/* @__PURE__ */ jsx(DialogPrimitive.Close, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
1183
|
-
"button",
|
|
1184
|
-
{
|
|
1185
|
-
type: "button",
|
|
1186
|
-
className: "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2",
|
|
1187
|
-
children: "H\u1EE7y"
|
|
1188
|
-
}
|
|
1189
|
-
) }),
|
|
1190
|
-
/* @__PURE__ */ jsx(
|
|
1191
|
-
"button",
|
|
1192
|
-
{
|
|
1193
|
-
type: "submit",
|
|
1194
|
-
className: "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground shadow hover:bg-primary/90 h-9 px-4 py-2",
|
|
1195
|
-
children: initialData ? "L\u01B0u thay \u0111\u1ED5i" : "Th\xEAm m\u1EDBi"
|
|
1196
|
-
}
|
|
1197
|
-
)
|
|
1198
|
-
] })
|
|
1199
|
-
]
|
|
1200
|
-
}
|
|
1201
|
-
),
|
|
1202
|
-
/* @__PURE__ */ jsxs(DialogPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground", children: [
|
|
1203
|
-
/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }),
|
|
1204
|
-
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
|
|
1205
|
-
] })
|
|
1206
|
-
] })
|
|
1207
|
-
] }) });
|
|
1208
|
-
}
|
|
1209
|
-
var containerVariants = {
|
|
1210
|
-
hidden: { opacity: 0 },
|
|
1211
|
-
visible: {
|
|
1212
|
-
opacity: 1,
|
|
1213
|
-
transition: {
|
|
1214
|
-
staggerChildren: 0.05,
|
|
1215
|
-
delayChildren: 0.1
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
};
|
|
1219
|
-
var itemVariants = {
|
|
1220
|
-
hidden: { opacity: 0, scale: 0.9 },
|
|
1221
|
-
visible: {
|
|
1222
|
-
opacity: 1,
|
|
1223
|
-
scale: 1,
|
|
1224
|
-
transition: {
|
|
1225
|
-
duration: 0.2,
|
|
1226
|
-
ease: "easeOut"
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
};
|
|
1230
|
-
function QuickAccessMenu({
|
|
1231
|
-
items,
|
|
1232
|
-
onItemClick,
|
|
1233
|
-
onItemsChange,
|
|
1234
|
-
className,
|
|
1235
|
-
editable = false,
|
|
1236
|
-
availableFeatures
|
|
1237
|
-
}) {
|
|
1238
|
-
const [isEditing, setIsEditing] = useState(false);
|
|
1239
|
-
const [dialogOpen, setDialogOpen] = useState(false);
|
|
1240
|
-
const [editingItem, setEditingItem] = useState(
|
|
1241
|
-
void 0
|
|
1242
|
-
);
|
|
1243
|
-
const handleAddStart = () => {
|
|
1244
|
-
setEditingItem(void 0);
|
|
1245
|
-
setDialogOpen(true);
|
|
1246
|
-
};
|
|
1247
|
-
const handleEditStart = (item, e) => {
|
|
1248
|
-
e.stopPropagation();
|
|
1249
|
-
setEditingItem(item);
|
|
1250
|
-
setDialogOpen(true);
|
|
1251
|
-
};
|
|
1252
|
-
const handleDelete = (itemId, e) => {
|
|
1253
|
-
e.stopPropagation();
|
|
1254
|
-
if (confirm("B\u1EA1n c\xF3 ch\u1EAFc mu\u1ED1n x\xF3a l\u1ED1i t\u1EAFt n\xE0y?")) {
|
|
1255
|
-
onItemsChange?.(items.filter((item) => item.id !== itemId));
|
|
1256
|
-
}
|
|
1257
|
-
};
|
|
1258
|
-
const handleSave = (data) => {
|
|
1259
|
-
const isDuplicate = items.some(
|
|
1260
|
-
(item) => item.href === data.href && item.id !== editingItem?.id
|
|
1261
|
-
);
|
|
1262
|
-
if (isDuplicate) {
|
|
1263
|
-
alert("L\u1ED1i t\u1EAFt n\xE0y \u0111\xE3 t\u1ED3n t\u1EA1i!");
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
if (editingItem) {
|
|
1267
|
-
onItemsChange?.(
|
|
1268
|
-
items.map(
|
|
1269
|
-
(item) => item.id === editingItem.id ? { ...item, ...data } : item
|
|
1270
|
-
)
|
|
1271
|
-
);
|
|
1272
|
-
} else {
|
|
1273
|
-
const newItem = {
|
|
1274
|
-
...data,
|
|
1275
|
-
id: `custom-${Date.now()}`
|
|
1276
|
-
};
|
|
1277
|
-
onItemsChange?.([...items, newItem]);
|
|
1278
|
-
}
|
|
1279
|
-
};
|
|
1280
|
-
if (items.length === 0 && !editable) {
|
|
1281
|
-
return null;
|
|
1282
|
-
}
|
|
1283
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), children: [
|
|
1284
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx("h3", { className: "text-slate-900 dark:text-white text-lg font-bold", children: "Quick Shortcuts" }) }),
|
|
1285
|
-
/* @__PURE__ */ jsx(
|
|
1286
|
-
motion.div,
|
|
1287
|
-
{
|
|
1288
|
-
variants: containerVariants,
|
|
1289
|
-
initial: "hidden",
|
|
1290
|
-
animate: "visible",
|
|
1291
|
-
className: "grid grid-cols-2 gap-4 sm:grid-cols-4",
|
|
1292
|
-
children: /* @__PURE__ */ jsxs(AnimatePresence, { mode: "popLayout", children: [
|
|
1293
|
-
items.map((item) => /* @__PURE__ */ jsxs(
|
|
1294
|
-
motion.button,
|
|
1295
|
-
{
|
|
1296
|
-
variants: itemVariants,
|
|
1297
|
-
whileHover: { scale: 1.02 },
|
|
1298
|
-
whileTap: { scale: 0.98 },
|
|
1299
|
-
onClick: () => {
|
|
1300
|
-
if (!isEditing) {
|
|
1301
|
-
onItemClick?.(item);
|
|
1302
|
-
}
|
|
1303
|
-
},
|
|
1304
|
-
className: cn(
|
|
1305
|
-
"group relative flex flex-col items-center justify-center gap-3 bg-white dark:bg-slate-800 p-5 rounded-xl border border-slate-200 dark:border-slate-700 shadow-sm hover:shadow-md hover:border-primary/50 transition-all text-left",
|
|
1306
|
-
isEditing && "cursor-default opacity-90 hover:opacity-100"
|
|
1307
|
-
),
|
|
1308
|
-
children: [
|
|
1309
|
-
isEditing && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1310
|
-
/* @__PURE__ */ jsx(
|
|
1311
|
-
"div",
|
|
1312
|
-
{
|
|
1313
|
-
onClick: (e) => handleDelete(item.id, e),
|
|
1314
|
-
className: "absolute -right-2 -top-2 z-10 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full bg-destructive text-white shadow-sm ring-2 ring-white hover:bg-destructive/90",
|
|
1315
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
|
|
1316
|
-
}
|
|
1317
|
-
),
|
|
1318
|
-
/* @__PURE__ */ jsx(
|
|
1319
|
-
"div",
|
|
1320
|
-
{
|
|
1321
|
-
onClick: (e) => handleEditStart(item, e),
|
|
1322
|
-
className: "absolute -left-2 -top-2 z-10 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full bg-background text-foreground shadow-sm ring-2 ring-white hover:bg-muted",
|
|
1323
|
-
children: /* @__PURE__ */ jsx(Pencil, { className: "h-3 w-3" })
|
|
1324
|
-
}
|
|
1325
|
-
)
|
|
1326
|
-
] }),
|
|
1327
|
-
editable && /* @__PURE__ */ jsx(
|
|
1328
|
-
"span",
|
|
1329
|
-
{
|
|
1330
|
-
onClick: () => setIsEditing(!isEditing),
|
|
1331
|
-
className: "absolute top-2 right-2 text-slate-300 dark:text-slate-600 cursor-move opacity-100 hover:text-primary transition-colors",
|
|
1332
|
-
children: /* @__PURE__ */ jsx(GripVertical, { size: 18 })
|
|
1333
|
-
}
|
|
1334
|
-
),
|
|
1335
|
-
/* @__PURE__ */ jsx(
|
|
1336
|
-
"div",
|
|
1337
|
-
{
|
|
1338
|
-
className: cn(
|
|
1339
|
-
"size-12 rounded-full flex items-center justify-center group-hover:scale-110 transition-transform",
|
|
1340
|
-
colorClasses[item.color || "blue"] || colorClasses.blue
|
|
1341
|
-
),
|
|
1342
|
-
children: iconMap[item.icon] || iconMap["document-text"]
|
|
1343
|
-
}
|
|
1344
|
-
),
|
|
1345
|
-
/* @__PURE__ */ jsx("span", { className: "text-slate-700 dark:text-slate-200 font-semibold text-sm", children: item.label })
|
|
1346
|
-
]
|
|
1347
|
-
},
|
|
1348
|
-
item.id
|
|
1349
|
-
)),
|
|
1350
|
-
editable && /* @__PURE__ */ jsxs(
|
|
1351
|
-
motion.button,
|
|
1352
|
-
{
|
|
1353
|
-
variants: itemVariants,
|
|
1354
|
-
onClick: handleAddStart,
|
|
1355
|
-
whileHover: { scale: 1.02 },
|
|
1356
|
-
whileTap: { scale: 0.98 },
|
|
1357
|
-
className: "flex flex-col items-center justify-center gap-2 bg-slate-50 dark:bg-slate-800/50 p-5 rounded-xl border-2 border-dashed border-slate-300 dark:border-slate-600 hover:border-primary hover:bg-slate-100 dark:hover:bg-slate-800 transition-all text-slate-500 hover:text-primary cursor-pointer group h-full min-h-[140px]",
|
|
1358
|
-
children: [
|
|
1359
|
-
/* @__PURE__ */ jsx(Plus, { className: "h-8 w-8 group-hover:scale-110 transition-transform" }),
|
|
1360
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Add Shortcut" })
|
|
1361
|
-
]
|
|
1362
|
-
}
|
|
1363
|
-
)
|
|
1364
|
-
] })
|
|
1365
|
-
}
|
|
1366
|
-
),
|
|
1367
|
-
/* @__PURE__ */ jsx(
|
|
1368
|
-
QuickAccessDialog,
|
|
1369
|
-
{
|
|
1370
|
-
open: dialogOpen,
|
|
1371
|
-
onOpenChange: setDialogOpen,
|
|
1372
|
-
onSubmit: handleSave,
|
|
1373
|
-
initialData: editingItem,
|
|
1374
|
-
availableFeatures
|
|
1375
|
-
}
|
|
1376
|
-
)
|
|
1377
|
-
] });
|
|
1378
|
-
}
|
|
1379
|
-
var DEFAULT_QUICK_ACCESS = [
|
|
1380
|
-
{
|
|
1381
|
-
id: "pos",
|
|
1382
|
-
label: "B\xE1n h\xE0ng",
|
|
1383
|
-
href: "/sales/pos",
|
|
1384
|
-
icon: "shopping-cart",
|
|
1385
|
-
color: "green"
|
|
1386
|
-
},
|
|
1387
|
-
{
|
|
1388
|
-
id: "orders",
|
|
1389
|
-
label: "\u0110\u01A1n h\xE0ng",
|
|
1390
|
-
href: "/sales-orders",
|
|
1391
|
-
icon: "clipboard-list",
|
|
1392
|
-
color: "blue"
|
|
1393
|
-
},
|
|
1394
|
-
{
|
|
1395
|
-
id: "customers",
|
|
1396
|
-
label: "Kh\xE1ch h\xE0ng",
|
|
1397
|
-
href: "/customers",
|
|
1398
|
-
icon: "users",
|
|
1399
|
-
color: "purple"
|
|
1400
|
-
},
|
|
1401
|
-
{
|
|
1402
|
-
id: "inventory",
|
|
1403
|
-
label: "T\u1ED3n kho",
|
|
1404
|
-
href: "/inventory",
|
|
1405
|
-
icon: "package",
|
|
1406
|
-
color: "orange"
|
|
1407
|
-
},
|
|
1408
|
-
{
|
|
1409
|
-
id: "reports",
|
|
1410
|
-
label: "B\xE1o c\xE1o",
|
|
1411
|
-
href: "/reports",
|
|
1412
|
-
icon: "chart-bar",
|
|
1413
|
-
color: "teal"
|
|
1414
|
-
},
|
|
1415
|
-
{
|
|
1416
|
-
id: "finance",
|
|
1417
|
-
label: "Thu chi",
|
|
1418
|
-
href: "/finance",
|
|
1419
|
-
icon: "currency-dollar",
|
|
1420
|
-
color: "pink"
|
|
1421
|
-
},
|
|
1422
|
-
{
|
|
1423
|
-
id: "products",
|
|
1424
|
-
label: "S\u1EA3n ph\u1EA9m",
|
|
1425
|
-
href: "/crud/products",
|
|
1426
|
-
icon: "package",
|
|
1427
|
-
color: "indigo"
|
|
1428
|
-
},
|
|
1429
|
-
{
|
|
1430
|
-
id: "settings",
|
|
1431
|
-
label: "C\xE0i \u0111\u1EB7t",
|
|
1432
|
-
href: "/admin",
|
|
1433
|
-
icon: "cog",
|
|
1434
|
-
color: "red"
|
|
1435
|
-
}
|
|
1436
|
-
];
|
|
1437
|
-
var LOCAL_STORAGE_KEY = "goerp_home_preferences";
|
|
1438
|
-
async function fetchPreferences(url) {
|
|
1439
|
-
const res = await fetch(url);
|
|
1440
|
-
if (!res.ok) {
|
|
1441
|
-
throw new Error("Failed to fetch preferences");
|
|
1442
|
-
}
|
|
1443
|
-
return res.json();
|
|
1444
|
-
}
|
|
1445
|
-
function useWidgetPreferences({
|
|
1446
|
-
userId,
|
|
1447
|
-
apiEndpoint = "/api/user/preferences",
|
|
1448
|
-
fallbackToLocalStorage = false
|
|
1449
|
-
} = {}) {
|
|
1450
|
-
const [localPreferences, setLocalPreferences] = useState(null);
|
|
1451
|
-
const { data, error, isLoading, mutate } = useSWR(
|
|
1452
|
-
userId ? `${apiEndpoint}?userId=${userId}` : null,
|
|
1453
|
-
fetchPreferences,
|
|
1454
|
-
{
|
|
1455
|
-
revalidateOnFocus: false,
|
|
1456
|
-
// Only use localPreferences as fallback if explicitly enabled
|
|
1457
|
-
fallbackData: fallbackToLocalStorage ? localPreferences || void 0 : void 0
|
|
1458
|
-
}
|
|
1459
|
-
);
|
|
1460
|
-
const storageKey = userId ? `${LOCAL_STORAGE_KEY}_${userId}` : LOCAL_STORAGE_KEY;
|
|
1461
|
-
useEffect(() => {
|
|
1462
|
-
if (fallbackToLocalStorage && typeof window !== "undefined") {
|
|
1463
|
-
try {
|
|
1464
|
-
const stored = localStorage.getItem(storageKey);
|
|
1465
|
-
if (stored) {
|
|
1466
|
-
setLocalPreferences(JSON.parse(stored));
|
|
1467
|
-
} else {
|
|
1468
|
-
setLocalPreferences(null);
|
|
1469
|
-
}
|
|
1470
|
-
} catch {
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
}, [fallbackToLocalStorage, storageKey]);
|
|
1474
|
-
const preferences = data || localPreferences || {
|
|
1475
|
-
widgets: DEFAULT_WIDGETS,
|
|
1476
|
-
quickAccess: []
|
|
1477
|
-
};
|
|
1478
|
-
const savePreferences = useCallback(
|
|
1479
|
-
async (newPreferences) => {
|
|
1480
|
-
mutate(newPreferences, false);
|
|
1481
|
-
if (fallbackToLocalStorage && typeof window !== "undefined") {
|
|
1482
|
-
try {
|
|
1483
|
-
localStorage.setItem(storageKey, JSON.stringify(newPreferences));
|
|
1484
|
-
setLocalPreferences(newPreferences);
|
|
1485
|
-
} catch {
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
if (userId) {
|
|
1489
|
-
try {
|
|
1490
|
-
const res = await fetch(apiEndpoint, {
|
|
1491
|
-
method: "PUT",
|
|
1492
|
-
headers: { "Content-Type": "application/json" },
|
|
1493
|
-
body: JSON.stringify({
|
|
1494
|
-
userId,
|
|
1495
|
-
...newPreferences
|
|
1496
|
-
})
|
|
1497
|
-
});
|
|
1498
|
-
if (!res.ok) {
|
|
1499
|
-
throw new Error("Failed to save preferences");
|
|
1500
|
-
}
|
|
1501
|
-
mutate();
|
|
1502
|
-
} catch (err) {
|
|
1503
|
-
console.error("Failed to save preferences to API:", err);
|
|
1504
|
-
mutate();
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
},
|
|
1508
|
-
[userId, apiEndpoint, fallbackToLocalStorage, mutate, storageKey]
|
|
1509
|
-
);
|
|
1510
|
-
const updateWidgets = useCallback(
|
|
1511
|
-
async (widgets) => {
|
|
1512
|
-
await savePreferences({
|
|
1513
|
-
...preferences,
|
|
1514
|
-
widgets
|
|
1515
|
-
});
|
|
1516
|
-
},
|
|
1517
|
-
[preferences, savePreferences]
|
|
1518
|
-
);
|
|
1519
|
-
const updateQuickAccess = useCallback(
|
|
1520
|
-
async (quickAccess) => {
|
|
1521
|
-
await savePreferences({
|
|
1522
|
-
...preferences,
|
|
1523
|
-
quickAccess
|
|
1524
|
-
});
|
|
1525
|
-
},
|
|
1526
|
-
[preferences, savePreferences]
|
|
1527
|
-
);
|
|
1528
|
-
const resetToDefaults = useCallback(async () => {
|
|
1529
|
-
await savePreferences({
|
|
1530
|
-
widgets: DEFAULT_WIDGETS,
|
|
1531
|
-
quickAccess: []
|
|
1532
|
-
});
|
|
1533
|
-
}, [savePreferences]);
|
|
1534
|
-
return {
|
|
1535
|
-
widgets: preferences.widgets,
|
|
1536
|
-
quickAccess: preferences.quickAccess,
|
|
1537
|
-
loading: isLoading,
|
|
1538
|
-
error: error?.message || null,
|
|
1539
|
-
updateWidgets,
|
|
1540
|
-
updateQuickAccess,
|
|
1541
|
-
resetToDefaults
|
|
1542
|
-
};
|
|
1543
|
-
}
|
|
1544
|
-
function HomePage({
|
|
1545
|
-
userName = "B\u1EA1n",
|
|
1546
|
-
userId,
|
|
1547
|
-
widgetData: initialWidgetData,
|
|
1548
|
-
availableFeatures,
|
|
1549
|
-
widgetDataLoading = false,
|
|
1550
|
-
showFeatureShowcase = false,
|
|
1551
|
-
className,
|
|
1552
|
-
basePath = "",
|
|
1553
|
-
dashboardApiUrl
|
|
1554
|
-
}) {
|
|
1555
|
-
const router = useRouter();
|
|
1556
|
-
const { data: fetchedWidgetData, isLoading: isFetching } = useSWR(
|
|
1557
|
-
initialWidgetData ? null : dashboardApiUrl,
|
|
1558
|
-
async (url) => {
|
|
1559
|
-
const res = await fetch(url);
|
|
1560
|
-
if (!res.ok) throw new Error("Failed to fetch widget data");
|
|
1561
|
-
return res.json();
|
|
1562
|
-
},
|
|
1563
|
-
{
|
|
1564
|
-
revalidateOnFocus: false,
|
|
1565
|
-
dedupingInterval: 6e4
|
|
1566
|
-
// 1 minute
|
|
1567
|
-
}
|
|
1568
|
-
);
|
|
1569
|
-
const {
|
|
1570
|
-
widgets,
|
|
1571
|
-
quickAccess,
|
|
1572
|
-
loading: prefsLoading,
|
|
1573
|
-
updateWidgets,
|
|
1574
|
-
updateQuickAccess
|
|
1575
|
-
} = useWidgetPreferences({
|
|
1576
|
-
userId
|
|
1577
|
-
});
|
|
1578
|
-
const filteredFeatures = availableFeatures?.filter(
|
|
1579
|
-
(f) => f.href !== "/home" && f.href !== basePath + "/home" && f.href !== "/"
|
|
1580
|
-
);
|
|
1581
|
-
const handleQuickAccessClick = (item) => {
|
|
1582
|
-
router.push(`${basePath}${item.href}`);
|
|
1583
|
-
};
|
|
1584
|
-
const getAuthorizedDefaults = () => {
|
|
1585
|
-
if (filteredFeatures && filteredFeatures.length > 0) {
|
|
1586
|
-
const colors = [
|
|
1587
|
-
"blue",
|
|
1588
|
-
"green",
|
|
1589
|
-
"purple",
|
|
1590
|
-
"orange",
|
|
1591
|
-
"teal",
|
|
1592
|
-
"pink",
|
|
1593
|
-
"indigo",
|
|
1594
|
-
"red"
|
|
1595
|
-
];
|
|
1596
|
-
return filteredFeatures.slice(0, 5).map((feature, index) => ({
|
|
1597
|
-
id: `default-${index}`,
|
|
1598
|
-
label: feature.label,
|
|
1599
|
-
href: feature.href,
|
|
1600
|
-
icon: feature.icon || "document-text",
|
|
1601
|
-
color: colors[index % colors.length]
|
|
1602
|
-
}));
|
|
1603
|
-
}
|
|
1604
|
-
return [];
|
|
1605
|
-
};
|
|
1606
|
-
const displayQuickAccess = quickAccess && quickAccess.length > 0 ? quickAccess : getAuthorizedDefaults();
|
|
1607
|
-
return /* @__PURE__ */ jsx(
|
|
1608
|
-
"div",
|
|
1609
|
-
{
|
|
1610
|
-
className: cn(
|
|
1611
|
-
"min-h-screen bg-slate-50/50 dark:bg-slate-900/50",
|
|
1612
|
-
className
|
|
1613
|
-
),
|
|
1614
|
-
children: /* @__PURE__ */ jsxs("div", { className: "w-full mx-auto px-4 md:px-6 lg:px-8 py-6 space-y-8", children: [
|
|
1615
|
-
/* @__PURE__ */ jsx(WelcomeCard, { userName }),
|
|
1616
|
-
false,
|
|
1617
|
-
/* @__PURE__ */ jsx("div", { className: "animate-in fade-in slide-in-from-bottom-4 duration-500 delay-300", children: /* @__PURE__ */ jsx(
|
|
1618
|
-
QuickAccessMenu,
|
|
1619
|
-
{
|
|
1620
|
-
items: displayQuickAccess,
|
|
1621
|
-
onItemClick: handleQuickAccessClick,
|
|
1622
|
-
editable: true,
|
|
1623
|
-
onItemsChange: updateQuickAccess,
|
|
1624
|
-
availableFeatures: filteredFeatures
|
|
1625
|
-
}
|
|
1626
|
-
) }),
|
|
1627
|
-
showFeatureShowcase && /* @__PURE__ */ jsx("div", { className: "animate-in fade-in slide-in-from-bottom-4 duration-500 delay-500 min-h-[400px]", children: /* @__PURE__ */ jsx(FeatureShowcase, {}) })
|
|
1628
|
-
] })
|
|
1629
|
-
}
|
|
1630
|
-
);
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
export { BaseWidget, CustomersWidget, DEFAULT_QUICK_ACCESS, DEFAULT_WIDGETS, ERP_FEATURES, FeatureShowcase, HomePage, OrdersWidget, QuickAccessMenu, RevenueWidget, StockWidget, WIDGET_LABELS, WelcomeCard, WidgetContainer, WidgetStat, useWidgetPreferences };
|
|
1634
|
-
//# sourceMappingURL=index.mjs.map
|
|
1635
|
-
//# sourceMappingURL=index.mjs.map
|