@goplusvn/core 0.1.63 → 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="h-4 w-4
|
|
141
|
-
<span className="hidden
|
|
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="h-4 w-4
|
|
372
|
-
<span className="hidden
|
|
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,33 +53,44 @@ 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 flex-col xl:flex-row items-start xl:items-center justify-between w-full gap-3 xl:gap-
|
|
64
|
+
<div className="flex flex-col xl:flex-row items-start xl:items-center justify-between w-full gap-3 xl:gap-3">
|
|
52
65
|
{/* Tìm kiếm + bộ lọc chính */}
|
|
53
|
-
<div className="flex w-full min-w-0 xl:w-auto flex-1 items-center gap-2
|
|
66
|
+
<div className="flex w-full min-w-0 xl:w-auto flex-1 items-center gap-2">
|
|
54
67
|
<div className="relative w-full xl:flex-[2] xl:min-w-[220px] max-w-none">
|
|
55
|
-
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
68
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
|
|
56
69
|
<Input
|
|
57
70
|
placeholder="Tìm người dùng..."
|
|
58
71
|
value={localSearch}
|
|
59
72
|
onChange={(e) => setLocalSearch(e.target.value)}
|
|
60
|
-
className="pl-9 h-9 w-full
|
|
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"
|
|
61
74
|
/>
|
|
62
75
|
{localSearch && (
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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>
|
|
71
87
|
)}
|
|
72
88
|
</div>
|
|
73
89
|
|
|
74
|
-
|
|
90
|
+
{/* Desktop Filters */}
|
|
91
|
+
<div className="hidden min-w-0 grow shrink basis-[150px] xl:block">
|
|
75
92
|
<Select value={userType} onValueChange={onUserTypeChange}>
|
|
76
|
-
<SelectTrigger className="h-9 w-full
|
|
93
|
+
<SelectTrigger className="h-9 w-full min-w-0 shadow-sm">
|
|
77
94
|
<SelectValue placeholder="Loại tài khoản" className="truncate" />
|
|
78
95
|
</SelectTrigger>
|
|
79
96
|
<SelectContent className="rounded-lg">
|
|
@@ -85,9 +102,9 @@ export function UserToolbar({
|
|
|
85
102
|
</Select>
|
|
86
103
|
</div>
|
|
87
104
|
|
|
88
|
-
<div className="
|
|
105
|
+
<div className="hidden min-w-0 grow shrink basis-[150px] xl:block">
|
|
89
106
|
<Select onValueChange={onRoleChange}>
|
|
90
|
-
<SelectTrigger className="h-9 w-full
|
|
107
|
+
<SelectTrigger className="h-9 w-full min-w-0 shadow-sm">
|
|
91
108
|
<SelectValue placeholder="Lọc theo vai trò" className="truncate" />
|
|
92
109
|
</SelectTrigger>
|
|
93
110
|
<SelectContent className="rounded-lg">
|
|
@@ -103,21 +120,103 @@ export function UserToolbar({
|
|
|
103
120
|
</div>
|
|
104
121
|
|
|
105
122
|
{/* Hành động */}
|
|
106
|
-
<div className="flex items-center justify-between
|
|
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>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
107
205
|
<div className="flex items-center gap-2">
|
|
108
|
-
|
|
206
|
+
{/* View Toggle */}
|
|
207
|
+
<div className="flex items-center rounded-md border p-0.5 bg-muted/30 shrink-0">
|
|
109
208
|
<Button
|
|
110
209
|
variant={viewMode === "table" ? "secondary" : "ghost"}
|
|
111
|
-
size="
|
|
112
|
-
className="h-
|
|
210
|
+
size="icon"
|
|
211
|
+
className="h-7 w-7 shadow-none"
|
|
113
212
|
onClick={() => onViewModeChange("table")}
|
|
114
213
|
>
|
|
115
214
|
<List className="h-4 w-4" />
|
|
116
215
|
</Button>
|
|
117
216
|
<Button
|
|
118
217
|
variant={viewMode === "grid" ? "secondary" : "ghost"}
|
|
119
|
-
size="
|
|
120
|
-
className="h-
|
|
218
|
+
size="icon"
|
|
219
|
+
className="h-7 w-7 shadow-none"
|
|
121
220
|
onClick={() => onViewModeChange("grid")}
|
|
122
221
|
>
|
|
123
222
|
<LayoutGrid className="h-4 w-4" />
|
|
@@ -125,7 +224,7 @@ export function UserToolbar({
|
|
|
125
224
|
</div>
|
|
126
225
|
|
|
127
226
|
{permissions.export && (
|
|
128
|
-
<div className="
|
|
227
|
+
<div className="shrink-0">
|
|
129
228
|
<CrudExportButton
|
|
130
229
|
endpoint="/api/crud/users/export"
|
|
131
230
|
search={localSearch}
|
|
@@ -135,8 +234,12 @@ export function UserToolbar({
|
|
|
135
234
|
)}
|
|
136
235
|
|
|
137
236
|
{permissions.create && (
|
|
138
|
-
<Button
|
|
139
|
-
|
|
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" />
|
|
140
243
|
<span className="hidden sm:inline">Thêm mới</span>
|
|
141
244
|
</Button>
|
|
142
245
|
)}
|
|
@@ -145,3 +248,4 @@ export function UserToolbar({
|
|
|
145
248
|
</div>
|
|
146
249
|
);
|
|
147
250
|
}
|
|
251
|
+
|