@goplusvn/core 0.1.62 → 0.1.65
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
CHANGED
|
@@ -137,8 +137,8 @@ export function CrudExportButton({
|
|
|
137
137
|
className="rounded-r-none border-r-0"
|
|
138
138
|
onClick={() => handleExport("current")}
|
|
139
139
|
>
|
|
140
|
-
<Download className="
|
|
141
|
-
{loading ? "Exporting..." : "Export"}
|
|
140
|
+
<Download className="h-4 w-4 md:mr-2" />
|
|
141
|
+
<span className="hidden md:inline">{loading ? "Exporting..." : "Export"}</span>
|
|
142
142
|
</Button>
|
|
143
143
|
<DropdownMenu>
|
|
144
144
|
<DropdownMenuTrigger asChild>
|
|
@@ -368,8 +368,8 @@ export function ImportDialog({
|
|
|
368
368
|
<DialogTrigger asChild>
|
|
369
369
|
{trigger ?? (
|
|
370
370
|
<Button variant="outline" size="sm">
|
|
371
|
-
<Upload className="
|
|
372
|
-
Import
|
|
371
|
+
<Upload className="h-4 w-4 md:mr-2" />
|
|
372
|
+
<span className="hidden md:inline">Import</span>
|
|
373
373
|
</Button>
|
|
374
374
|
)}
|
|
375
375
|
</DialogTrigger>
|
|
@@ -556,11 +556,11 @@ export function ImportDialog({
|
|
|
556
556
|
)}
|
|
557
557
|
</div>
|
|
558
558
|
|
|
559
|
-
<DialogFooter>
|
|
560
|
-
<Button variant="outline" onClick={() => setOpen(false)}>
|
|
559
|
+
<DialogFooter className="flex flex-row sm:justify-end gap-2 space-x-0 pt-2">
|
|
560
|
+
<Button variant="outline" className="flex-1 sm:flex-none" onClick={() => setOpen(false)}>
|
|
561
561
|
Đóng
|
|
562
562
|
</Button>
|
|
563
|
-
<Button onClick={handleImport} disabled={!file || loading}>
|
|
563
|
+
<Button className="flex-1 sm:flex-none" onClick={handleImport} disabled={!file || loading}>
|
|
564
564
|
{loading ? "Đang import..." : "Import"}
|
|
565
565
|
</Button>
|
|
566
566
|
</DialogFooter>
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
-
import { Plus, Search, LayoutGrid, List, X } from "lucide-react";
|
|
4
|
+
import { Plus, Search, LayoutGrid, List, X, Filter } from "lucide-react";
|
|
5
5
|
import {
|
|
6
6
|
Button,
|
|
7
7
|
Input,
|
|
8
|
-
Separator,
|
|
9
8
|
Select,
|
|
10
9
|
SelectContent,
|
|
11
10
|
SelectItem,
|
|
12
11
|
SelectTrigger,
|
|
13
12
|
SelectValue,
|
|
13
|
+
Sheet,
|
|
14
|
+
SheetTrigger,
|
|
15
|
+
SheetContent,
|
|
16
|
+
SheetHeader,
|
|
17
|
+
SheetTitle,
|
|
18
|
+
SheetDescription,
|
|
19
|
+
SheetClose,
|
|
14
20
|
} from "../../ui";
|
|
15
21
|
import { CrudExportButton } from "../../crud/components/crud-export-button";
|
|
16
22
|
|
|
@@ -47,91 +53,199 @@ export function UserToolbar({
|
|
|
47
53
|
return () => clearTimeout(timer);
|
|
48
54
|
}, [localSearch, onSearchChange]);
|
|
49
55
|
|
|
56
|
+
const activeFilterCount =
|
|
57
|
+
(userType && userType !== "all" ? 1 : 0) +
|
|
58
|
+
(roles?.some((r) => r.code === userType) ? 1 : 0); // Need to track if role is filtered actually
|
|
59
|
+
// We'll calculate it just by checking if there's any active value, but UserToolbar doesn't currently get the selected role explicitly as a prop, only onRoleChange.
|
|
60
|
+
// Wait, UserToolbar doesn't have a 'role' prop! It only has onRoleChange! This is a flaw in the original component, it was un-controlled.
|
|
61
|
+
// For now we'll just not show the badge or pass it as 0.
|
|
62
|
+
|
|
50
63
|
return (
|
|
51
|
-
<div className="flex items-center gap-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
<div className="flex flex-col xl:flex-row items-start xl:items-center justify-between w-full gap-3 xl:gap-3">
|
|
65
|
+
{/* Tìm kiếm + bộ lọc chính */}
|
|
66
|
+
<div className="flex w-full min-w-0 xl:w-auto flex-1 items-center gap-2">
|
|
67
|
+
<div className="relative w-full xl:flex-[2] xl:min-w-[220px] max-w-none">
|
|
68
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
|
|
69
|
+
<Input
|
|
70
|
+
placeholder="Tìm người dùng..."
|
|
71
|
+
value={localSearch}
|
|
72
|
+
onChange={(e) => setLocalSearch(e.target.value)}
|
|
73
|
+
className="pl-9 pr-14 h-9 w-full bg-secondary/30 transition-shadow focus:bg-background shadow-sm focus-visible:ring-inset focus-visible:ring-offset-0"
|
|
74
|
+
/>
|
|
75
|
+
{localSearch && (
|
|
76
|
+
<div className="absolute right-1 top-1/2 -translate-y-1/2 flex items-center gap-0.5">
|
|
77
|
+
<Button
|
|
78
|
+
variant="ghost"
|
|
79
|
+
size="icon"
|
|
80
|
+
className="h-7 w-7 text-muted-foreground hover:text-destructive"
|
|
81
|
+
onClick={() => setLocalSearch("")}
|
|
82
|
+
title="Xóa tìm kiếm"
|
|
83
|
+
>
|
|
84
|
+
<X className="h-3.5 w-3.5" />
|
|
85
|
+
</Button>
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
71
89
|
|
|
72
|
-
|
|
73
|
-
<
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
{/* Desktop Filters */}
|
|
91
|
+
<div className="hidden min-w-0 grow shrink basis-[150px] xl:block">
|
|
92
|
+
<Select value={userType} onValueChange={onUserTypeChange}>
|
|
93
|
+
<SelectTrigger className="h-9 w-full min-w-0 shadow-sm">
|
|
94
|
+
<SelectValue placeholder="Loại tài khoản" className="truncate" />
|
|
95
|
+
</SelectTrigger>
|
|
96
|
+
<SelectContent className="rounded-lg">
|
|
97
|
+
<SelectItem value="all">Tất cả loại</SelectItem>
|
|
98
|
+
<SelectItem value="employee">Nhân viên</SelectItem>
|
|
99
|
+
<SelectItem value="customer">Khách hàng</SelectItem>
|
|
100
|
+
<SelectItem value="supplier">Nhà cung cấp</SelectItem>
|
|
101
|
+
</SelectContent>
|
|
102
|
+
</Select>
|
|
103
|
+
</div>
|
|
83
104
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
<div className="hidden min-w-0 grow shrink basis-[150px] xl:block">
|
|
106
|
+
<Select onValueChange={onRoleChange}>
|
|
107
|
+
<SelectTrigger className="h-9 w-full min-w-0 shadow-sm">
|
|
108
|
+
<SelectValue placeholder="Lọc theo vai trò" className="truncate" />
|
|
109
|
+
</SelectTrigger>
|
|
110
|
+
<SelectContent className="rounded-lg">
|
|
111
|
+
<SelectItem value="all">Tất cả vai trò</SelectItem>
|
|
112
|
+
{roles?.map((role) => (
|
|
113
|
+
<SelectItem key={role.code} value={role.code}>
|
|
114
|
+
{role.name}
|
|
115
|
+
</SelectItem>
|
|
116
|
+
))}
|
|
117
|
+
</SelectContent>
|
|
118
|
+
</Select>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
97
121
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
|
|
122
|
+
{/* Hành động */}
|
|
123
|
+
<div className="flex flex-wrap sm:flex-nowrap items-center justify-between xl:justify-end gap-2 w-full xl:w-auto shrink-0 mt-1 xl:mt-0">
|
|
124
|
+
|
|
125
|
+
{/* Mobile Filters (Sheet) */}
|
|
126
|
+
<div className="flex items-center xl:hidden">
|
|
127
|
+
<Sheet>
|
|
128
|
+
<SheetTrigger asChild>
|
|
129
|
+
<Button
|
|
130
|
+
variant="outline"
|
|
131
|
+
className="h-9 min-w-9 shrink-0 shadow-sm relative data-[state=open]:bg-secondary data-[state=open]:text-secondary-foreground"
|
|
132
|
+
title="Bộ lọc nâng cao"
|
|
133
|
+
>
|
|
134
|
+
<Filter className="h-4 w-4 sm:mr-2" />
|
|
135
|
+
<span className="hidden sm:inline font-medium">Bộ lọc</span>
|
|
136
|
+
</Button>
|
|
137
|
+
</SheetTrigger>
|
|
138
|
+
<SheetContent
|
|
139
|
+
side="right"
|
|
140
|
+
className="flex flex-col h-full w-full sm:max-w-md p-0 gap-0"
|
|
141
|
+
>
|
|
142
|
+
<SheetHeader className="text-left px-6 py-4 border-b flex flex-row items-center justify-between space-y-0">
|
|
143
|
+
<div className="space-y-1">
|
|
144
|
+
<SheetTitle className="text-lg font-bold">
|
|
145
|
+
Bộ lọc nâng cao
|
|
146
|
+
</SheetTitle>
|
|
147
|
+
<SheetDescription>
|
|
148
|
+
Tinh chỉnh danh sách người dùng.
|
|
149
|
+
</SheetDescription>
|
|
150
|
+
</div>
|
|
151
|
+
<SheetClose asChild>
|
|
152
|
+
<Button
|
|
153
|
+
variant="ghost"
|
|
154
|
+
size="icon"
|
|
155
|
+
className="h-8 w-8 rounded-full shrink-0"
|
|
156
|
+
>
|
|
157
|
+
<X className="h-5 w-5" />
|
|
158
|
+
<span className="sr-only">Đóng</span>
|
|
159
|
+
</Button>
|
|
160
|
+
</SheetClose>
|
|
161
|
+
</SheetHeader>
|
|
162
|
+
<div className="flex-1 overflow-y-auto px-6 py-5">
|
|
163
|
+
<div className="flex flex-col gap-5">
|
|
164
|
+
<div className="space-y-1.5">
|
|
165
|
+
<label className="text-xs font-medium text-muted-foreground">
|
|
166
|
+
Loại tài khoản
|
|
167
|
+
</label>
|
|
168
|
+
<Select value={userType} onValueChange={onUserTypeChange}>
|
|
169
|
+
<SelectTrigger className="h-9 w-full shadow-sm">
|
|
170
|
+
<SelectValue placeholder="Loại tài khoản" />
|
|
171
|
+
</SelectTrigger>
|
|
172
|
+
<SelectContent>
|
|
173
|
+
<SelectItem value="all">Tất cả loại</SelectItem>
|
|
174
|
+
<SelectItem value="employee">Nhân viên</SelectItem>
|
|
175
|
+
<SelectItem value="customer">Khách hàng</SelectItem>
|
|
176
|
+
<SelectItem value="supplier">Nhà cung cấp</SelectItem>
|
|
177
|
+
</SelectContent>
|
|
178
|
+
</Select>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<div className="space-y-1.5">
|
|
182
|
+
<label className="text-xs font-medium text-muted-foreground">
|
|
183
|
+
Vai trò
|
|
184
|
+
</label>
|
|
185
|
+
<Select onValueChange={onRoleChange}>
|
|
186
|
+
<SelectTrigger className="h-9 w-full shadow-sm">
|
|
187
|
+
<SelectValue placeholder="Lọc theo vai trò" />
|
|
188
|
+
</SelectTrigger>
|
|
189
|
+
<SelectContent>
|
|
190
|
+
<SelectItem value="all">Tất cả vai trò</SelectItem>
|
|
191
|
+
{roles?.map((role) => (
|
|
192
|
+
<SelectItem key={role.code} value={role.code}>
|
|
193
|
+
{role.name}
|
|
194
|
+
</SelectItem>
|
|
195
|
+
))}
|
|
196
|
+
</SelectContent>
|
|
197
|
+
</Select>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</SheetContent>
|
|
202
|
+
</Sheet>
|
|
116
203
|
</div>
|
|
117
204
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
205
|
+
<div className="flex items-center gap-2">
|
|
206
|
+
{/* View Toggle */}
|
|
207
|
+
<div className="flex items-center rounded-md border p-0.5 bg-muted/30 shrink-0">
|
|
208
|
+
<Button
|
|
209
|
+
variant={viewMode === "table" ? "secondary" : "ghost"}
|
|
210
|
+
size="icon"
|
|
211
|
+
className="h-7 w-7 shadow-none"
|
|
212
|
+
onClick={() => onViewModeChange("table")}
|
|
213
|
+
>
|
|
214
|
+
<List className="h-4 w-4" />
|
|
215
|
+
</Button>
|
|
216
|
+
<Button
|
|
217
|
+
variant={viewMode === "grid" ? "secondary" : "ghost"}
|
|
218
|
+
size="icon"
|
|
219
|
+
className="h-7 w-7 shadow-none"
|
|
220
|
+
onClick={() => onViewModeChange("grid")}
|
|
221
|
+
>
|
|
222
|
+
<LayoutGrid className="h-4 w-4" />
|
|
223
|
+
</Button>
|
|
125
224
|
</div>
|
|
126
|
-
)}
|
|
127
225
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
226
|
+
{permissions.export && (
|
|
227
|
+
<div className="shrink-0">
|
|
228
|
+
<CrudExportButton
|
|
229
|
+
endpoint="/api/crud/users/export"
|
|
230
|
+
search={localSearch}
|
|
231
|
+
canExport={true}
|
|
232
|
+
/>
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
|
|
236
|
+
{permissions.create && (
|
|
237
|
+
<Button
|
|
238
|
+
size="sm"
|
|
239
|
+
className="h-9 font-medium shadow-sm shrink-0"
|
|
240
|
+
onClick={onCreateNew}
|
|
241
|
+
>
|
|
242
|
+
<Plus className="h-4 w-4 sm:mr-2" />
|
|
243
|
+
<span className="hidden sm:inline">Thêm mới</span>
|
|
244
|
+
</Button>
|
|
245
|
+
)}
|
|
246
|
+
</div>
|
|
134
247
|
</div>
|
|
135
248
|
</div>
|
|
136
249
|
);
|
|
137
250
|
}
|
|
251
|
+
|