@hed-hog/tag 0.0.278 → 0.0.285
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.
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
EmptyState,
|
|
5
|
+
Page,
|
|
4
6
|
PageHeader,
|
|
5
7
|
PaginationFooter,
|
|
6
8
|
SearchBar,
|
|
9
|
+
StatsCards,
|
|
7
10
|
} from '@/components/entity-list';
|
|
8
11
|
import {
|
|
9
12
|
AlertDialog,
|
|
@@ -20,15 +23,14 @@ import { Badge } from '@/components/ui/badge';
|
|
|
20
23
|
import { Button } from '@/components/ui/button';
|
|
21
24
|
import { Card, CardContent } from '@/components/ui/card';
|
|
22
25
|
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} from '@/components/ui/
|
|
26
|
+
Form,
|
|
27
|
+
FormControl,
|
|
28
|
+
FormField,
|
|
29
|
+
FormItem,
|
|
30
|
+
FormLabel,
|
|
31
|
+
FormMessage,
|
|
32
|
+
} from '@/components/ui/form';
|
|
30
33
|
import { Input } from '@/components/ui/input';
|
|
31
|
-
import { Label } from '@/components/ui/label';
|
|
32
34
|
import {
|
|
33
35
|
Select,
|
|
34
36
|
SelectContent,
|
|
@@ -36,12 +38,23 @@ import {
|
|
|
36
38
|
SelectTrigger,
|
|
37
39
|
SelectValue,
|
|
38
40
|
} from '@/components/ui/select';
|
|
41
|
+
import {
|
|
42
|
+
Sheet,
|
|
43
|
+
SheetContent,
|
|
44
|
+
SheetDescription,
|
|
45
|
+
SheetFooter,
|
|
46
|
+
SheetHeader,
|
|
47
|
+
SheetTitle,
|
|
48
|
+
} from '@/components/ui/sheet';
|
|
39
49
|
import { useDebounce } from '@/hooks/use-debounce';
|
|
40
50
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
41
|
-
import {
|
|
51
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
52
|
+
import { Edit, Save, Tag as TagIcon, Trash2 } from 'lucide-react';
|
|
42
53
|
import { useTranslations } from 'next-intl';
|
|
43
|
-
import { useEffect, useState } from 'react';
|
|
54
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
55
|
+
import { useForm } from 'react-hook-form';
|
|
44
56
|
import { toast } from 'sonner';
|
|
57
|
+
import { z } from 'zod';
|
|
45
58
|
|
|
46
59
|
type PaginationResult<T> = {
|
|
47
60
|
data: T[];
|
|
@@ -57,22 +70,44 @@ type Tag = {
|
|
|
57
70
|
status: 'active' | 'inactive';
|
|
58
71
|
};
|
|
59
72
|
|
|
73
|
+
const PAGE_SIZE_OPTIONS: number[] = [10, 20, 30, 40, 50];
|
|
74
|
+
|
|
60
75
|
export default function TagPage() {
|
|
61
76
|
const t = useTranslations('tag.Tag');
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
77
|
+
const formSchema = useMemo(
|
|
78
|
+
() =>
|
|
79
|
+
z.object({
|
|
80
|
+
slug: z.string().trim().min(2, t('validation.slugMin')),
|
|
81
|
+
color: z
|
|
82
|
+
.string()
|
|
83
|
+
.regex(/^#[0-9A-Fa-f]{6}$/, t('validation.colorInvalid')),
|
|
84
|
+
status: z.enum(['active', 'inactive']),
|
|
85
|
+
}),
|
|
86
|
+
[t]
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
type FormValues = z.infer<typeof formSchema>;
|
|
90
|
+
|
|
91
|
+
const [isSheetOpen, setIsSheetOpen] = useState(false);
|
|
92
|
+
const [editingTagId, setEditingTagId] = useState<number | null>(null);
|
|
66
93
|
const [searchTerm, setSearchTerm] = useState('');
|
|
67
94
|
const debouncedSearch = useDebounce(searchTerm);
|
|
68
95
|
const [page, setPage] = useState(1);
|
|
69
|
-
const [pageSize, setPageSize] = useState(10);
|
|
96
|
+
const [pageSize, setPageSize] = useState<number>(10);
|
|
70
97
|
const { request } = useApp();
|
|
71
98
|
|
|
72
|
-
const {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
99
|
+
const form = useForm<FormValues>({
|
|
100
|
+
resolver: zodResolver(formSchema),
|
|
101
|
+
defaultValues: {
|
|
102
|
+
slug: '',
|
|
103
|
+
color: '#000000',
|
|
104
|
+
status: 'active',
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const { data: tagResult, refetch: refetchTag } = useQuery<
|
|
109
|
+
PaginationResult<Tag>
|
|
110
|
+
>({
|
|
76
111
|
queryKey: ['tag', debouncedSearch, page, pageSize],
|
|
77
112
|
queryFn: async () => {
|
|
78
113
|
const response = await request({
|
|
@@ -85,13 +120,8 @@ export default function TagPage() {
|
|
|
85
120
|
});
|
|
86
121
|
return response.data as PaginationResult<Tag>;
|
|
87
122
|
},
|
|
88
|
-
initialData: {
|
|
89
|
-
data: [],
|
|
90
|
-
total: 0,
|
|
91
|
-
page: 1,
|
|
92
|
-
pageSize: 10,
|
|
93
|
-
},
|
|
94
123
|
});
|
|
124
|
+
const { data = [], total = 0 } = tagResult ?? {};
|
|
95
125
|
|
|
96
126
|
const { data: statsData, refetch: refetchStats } = useQuery<any>({
|
|
97
127
|
queryKey: ['tag-stats'],
|
|
@@ -103,51 +133,47 @@ export default function TagPage() {
|
|
|
103
133
|
},
|
|
104
134
|
});
|
|
105
135
|
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
if (data) {
|
|
108
|
-
setTags(data);
|
|
109
|
-
}
|
|
110
|
-
}, [data]);
|
|
111
|
-
|
|
112
136
|
const handleNewTag = (): void => {
|
|
113
|
-
|
|
137
|
+
form.reset({
|
|
114
138
|
slug: '',
|
|
115
139
|
color: '#000000',
|
|
116
140
|
status: 'active',
|
|
117
141
|
});
|
|
118
|
-
|
|
119
|
-
|
|
142
|
+
setEditingTagId(null);
|
|
143
|
+
setIsSheetOpen(true);
|
|
120
144
|
};
|
|
121
145
|
|
|
122
146
|
const handleEditTag = async (tag: Tag): Promise<void> => {
|
|
123
147
|
try {
|
|
124
|
-
const response = await request({
|
|
148
|
+
const response = await request<Tag>({
|
|
125
149
|
url: `/tag/${tag.id}`,
|
|
126
150
|
method: 'GET',
|
|
127
151
|
});
|
|
128
152
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
153
|
+
form.reset({
|
|
154
|
+
slug: response.data.slug ?? '',
|
|
155
|
+
color: response.data.color ?? '#000000',
|
|
156
|
+
status: response.data.status ?? 'active',
|
|
157
|
+
});
|
|
158
|
+
setEditingTagId(tag.id);
|
|
159
|
+
setIsSheetOpen(true);
|
|
132
160
|
} catch (error) {
|
|
133
161
|
console.error(error);
|
|
134
162
|
toast.error(t('errorLoading'));
|
|
135
163
|
}
|
|
136
164
|
};
|
|
137
165
|
|
|
138
|
-
const handleSaveTag = async () => {
|
|
139
|
-
if (!selectedTag) return;
|
|
140
|
-
|
|
166
|
+
const handleSaveTag = async (values: FormValues) => {
|
|
141
167
|
const payload = {
|
|
142
|
-
slug:
|
|
143
|
-
color:
|
|
144
|
-
status:
|
|
168
|
+
slug: values.slug,
|
|
169
|
+
color: values.color,
|
|
170
|
+
status: values.status,
|
|
145
171
|
};
|
|
146
172
|
|
|
147
173
|
try {
|
|
148
|
-
if (
|
|
174
|
+
if (editingTagId) {
|
|
149
175
|
await request({
|
|
150
|
-
url: `/tag/${
|
|
176
|
+
url: `/tag/${editingTagId}`,
|
|
151
177
|
method: 'PATCH',
|
|
152
178
|
data: payload,
|
|
153
179
|
});
|
|
@@ -161,7 +187,7 @@ export default function TagPage() {
|
|
|
161
187
|
toast.success(t('successCreate'));
|
|
162
188
|
}
|
|
163
189
|
|
|
164
|
-
|
|
190
|
+
setIsSheetOpen(false);
|
|
165
191
|
await refetchTag();
|
|
166
192
|
await refetchStats();
|
|
167
193
|
} catch (error) {
|
|
@@ -187,20 +213,19 @@ export default function TagPage() {
|
|
|
187
213
|
|
|
188
214
|
const handleSearchChange = (value: string): void => {
|
|
189
215
|
setSearchTerm(value);
|
|
216
|
+
setPage(1);
|
|
190
217
|
};
|
|
191
218
|
|
|
192
219
|
useEffect(() => {
|
|
193
|
-
refetchTag();
|
|
194
220
|
refetchStats();
|
|
195
|
-
}, [
|
|
221
|
+
}, [isSheetOpen, refetchStats]);
|
|
222
|
+
|
|
223
|
+
const isNewTag = editingTagId === null;
|
|
196
224
|
|
|
197
225
|
return (
|
|
198
|
-
<
|
|
226
|
+
<Page>
|
|
199
227
|
<PageHeader
|
|
200
|
-
breadcrumbs={[
|
|
201
|
-
{ label: 'Home', href: '/' },
|
|
202
|
-
{ label: t('description') },
|
|
203
|
-
]}
|
|
228
|
+
breadcrumbs={[{ label: 'Home', href: '/' }, { label: t('title') }]}
|
|
204
229
|
actions={[
|
|
205
230
|
{
|
|
206
231
|
label: t('newTag'),
|
|
@@ -212,142 +237,128 @@ export default function TagPage() {
|
|
|
212
237
|
description={t('description')}
|
|
213
238
|
/>
|
|
214
239
|
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
<
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
</p>
|
|
242
|
-
<p className="text-2xl font-bold">{statsData?.active || 0}</p>
|
|
243
|
-
</div>
|
|
244
|
-
</div>
|
|
245
|
-
</CardContent>
|
|
246
|
-
</Card>
|
|
247
|
-
|
|
248
|
-
<Card className="transition-shadow hover:shadow-md p-0">
|
|
249
|
-
<CardContent className="p-4">
|
|
250
|
-
<div className="flex items-center space-x-3">
|
|
251
|
-
<div className="rounded-full bg-gray-100 p-2 dark:bg-gray-900">
|
|
252
|
-
<TagIcon className="h-6 w-6 text-gray-600 dark:text-gray-400" />
|
|
253
|
-
</div>
|
|
254
|
-
<div>
|
|
255
|
-
<p className="text-sm font-medium text-muted-foreground">
|
|
256
|
-
{t('inactiveTags')}
|
|
257
|
-
</p>
|
|
258
|
-
<p className="text-2xl font-bold">{statsData?.inactive || 0}</p>
|
|
259
|
-
</div>
|
|
260
|
-
</div>
|
|
261
|
-
</CardContent>
|
|
262
|
-
</Card>
|
|
263
|
-
</div>
|
|
240
|
+
<StatsCards
|
|
241
|
+
stats={[
|
|
242
|
+
{
|
|
243
|
+
title: t('totalTags'),
|
|
244
|
+
value: statsData?.total || 0,
|
|
245
|
+
icon: <TagIcon className="h-5 w-5" />,
|
|
246
|
+
iconBgColor: 'bg-blue-100 dark:bg-blue-900',
|
|
247
|
+
iconColor: 'text-blue-600 dark:text-blue-400',
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
title: t('activeTags'),
|
|
251
|
+
value: statsData?.active || 0,
|
|
252
|
+
icon: <TagIcon className="h-5 w-5" />,
|
|
253
|
+
iconBgColor: 'bg-green-100 dark:bg-green-900',
|
|
254
|
+
iconColor: 'text-green-600 dark:text-green-400',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
title: t('inactiveTags'),
|
|
258
|
+
value: statsData?.inactive || 0,
|
|
259
|
+
icon: <TagIcon className="h-5 w-5" />,
|
|
260
|
+
iconBgColor: 'bg-gray-100 dark:bg-gray-900',
|
|
261
|
+
iconColor: 'text-gray-600 dark:text-gray-400',
|
|
262
|
+
},
|
|
263
|
+
]}
|
|
264
|
+
className="grid-cols-1 md:grid-cols-3"
|
|
265
|
+
/>
|
|
264
266
|
|
|
265
|
-
<
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
/>
|
|
272
|
-
</div>
|
|
267
|
+
<SearchBar
|
|
268
|
+
searchQuery={searchTerm}
|
|
269
|
+
onSearchChange={handleSearchChange}
|
|
270
|
+
onSearch={() => refetchTag()}
|
|
271
|
+
placeholder={t('searchPlaceholder')}
|
|
272
|
+
/>
|
|
273
273
|
|
|
274
|
-
<div
|
|
275
|
-
{
|
|
276
|
-
<
|
|
277
|
-
<
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
274
|
+
<div>
|
|
275
|
+
{data.length === 0 ? (
|
|
276
|
+
<EmptyState
|
|
277
|
+
icon={<TagIcon className="h-6 w-6" />}
|
|
278
|
+
title={t('noTagsFound')}
|
|
279
|
+
description={searchTerm ? t('adjustSearch') : t('createNewTag')}
|
|
280
|
+
actionLabel={searchTerm ? t('clearSearch') : t('newTag')}
|
|
281
|
+
onAction={() => {
|
|
282
|
+
if (searchTerm) {
|
|
283
|
+
setSearchTerm('');
|
|
284
|
+
setPage(1);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
handleNewTag();
|
|
288
|
+
}}
|
|
289
|
+
/>
|
|
287
290
|
) : (
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
<
|
|
296
|
-
<div
|
|
297
|
-
className="
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
<
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
</
|
|
291
|
+
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
|
292
|
+
{data.map((tag) => (
|
|
293
|
+
<Card
|
|
294
|
+
key={tag.id}
|
|
295
|
+
onDoubleClick={() => handleEditTag(tag)}
|
|
296
|
+
className="cursor-pointer transition-shadow hover:shadow-md"
|
|
297
|
+
>
|
|
298
|
+
<CardContent className="p-4">
|
|
299
|
+
<div className="flex items-start justify-between gap-2">
|
|
300
|
+
<div className="flex items-center gap-3">
|
|
301
|
+
<div
|
|
302
|
+
className="h-9 w-9 rounded-md border"
|
|
303
|
+
style={{ backgroundColor: tag.color }}
|
|
304
|
+
/>
|
|
305
|
+
<div className="min-w-0">
|
|
306
|
+
<h2 className="truncate text-base font-semibold">
|
|
307
|
+
#{tag.slug}
|
|
308
|
+
</h2>
|
|
309
|
+
<p className="text-xs text-muted-foreground">
|
|
310
|
+
{tag.color}
|
|
311
|
+
</p>
|
|
312
|
+
</div>
|
|
310
313
|
</div>
|
|
314
|
+
<Badge
|
|
315
|
+
variant={
|
|
316
|
+
tag.status === 'active' ? 'default' : 'secondary'
|
|
317
|
+
}
|
|
318
|
+
>
|
|
319
|
+
{tag.status === 'active' ? t('active') : t('inactive')}
|
|
320
|
+
</Badge>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<div className="mt-3 flex items-center justify-end gap-1">
|
|
324
|
+
<Button
|
|
325
|
+
variant="ghost"
|
|
326
|
+
size="icon"
|
|
327
|
+
onClick={() => handleEditTag(tag)}
|
|
328
|
+
>
|
|
329
|
+
<Edit className="h-4 w-4" />
|
|
330
|
+
</Button>
|
|
331
|
+
<AlertDialog>
|
|
332
|
+
<AlertDialogTrigger asChild>
|
|
333
|
+
<Button variant="ghost" size="icon">
|
|
334
|
+
<Trash2 className="h-4 w-4 text-destructive" />
|
|
335
|
+
</Button>
|
|
336
|
+
</AlertDialogTrigger>
|
|
337
|
+
<AlertDialogContent>
|
|
338
|
+
<AlertDialogHeader>
|
|
339
|
+
<AlertDialogTitle>
|
|
340
|
+
{t('confirmDelete')}
|
|
341
|
+
</AlertDialogTitle>
|
|
342
|
+
<AlertDialogDescription>
|
|
343
|
+
{t('deleteDescription')}
|
|
344
|
+
</AlertDialogDescription>
|
|
345
|
+
</AlertDialogHeader>
|
|
346
|
+
<AlertDialogFooter>
|
|
347
|
+
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
|
|
348
|
+
<AlertDialogAction
|
|
349
|
+
onClick={() => handleDeleteTag(tag.id)}
|
|
350
|
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
351
|
+
>
|
|
352
|
+
{t('delete')}
|
|
353
|
+
</AlertDialogAction>
|
|
354
|
+
</AlertDialogFooter>
|
|
355
|
+
</AlertDialogContent>
|
|
356
|
+
</AlertDialog>
|
|
311
357
|
</div>
|
|
312
|
-
</
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
size="icon"
|
|
317
|
-
onClick={() => handleEditTag(tag)}
|
|
318
|
-
>
|
|
319
|
-
<Edit className="h-4 w-4" />
|
|
320
|
-
</Button>
|
|
321
|
-
<AlertDialog>
|
|
322
|
-
<AlertDialogTrigger asChild>
|
|
323
|
-
<Button variant="ghost" size="icon">
|
|
324
|
-
<Trash2 className="h-4 w-4 text-destructive" />
|
|
325
|
-
</Button>
|
|
326
|
-
</AlertDialogTrigger>
|
|
327
|
-
<AlertDialogContent>
|
|
328
|
-
<AlertDialogHeader>
|
|
329
|
-
<AlertDialogTitle>
|
|
330
|
-
{t('confirmDelete')}
|
|
331
|
-
</AlertDialogTitle>
|
|
332
|
-
<AlertDialogDescription>
|
|
333
|
-
{t('deleteDescription')}
|
|
334
|
-
</AlertDialogDescription>
|
|
335
|
-
</AlertDialogHeader>
|
|
336
|
-
<AlertDialogFooter>
|
|
337
|
-
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
|
|
338
|
-
<AlertDialogAction
|
|
339
|
-
onClick={() => handleDeleteTag(tag.id)}
|
|
340
|
-
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
341
|
-
>
|
|
342
|
-
{t('delete')}
|
|
343
|
-
</AlertDialogAction>
|
|
344
|
-
</AlertDialogFooter>
|
|
345
|
-
</AlertDialogContent>
|
|
346
|
-
</AlertDialog>
|
|
347
|
-
</div>
|
|
348
|
-
</CardContent>
|
|
349
|
-
</Card>
|
|
350
|
-
))
|
|
358
|
+
</CardContent>
|
|
359
|
+
</Card>
|
|
360
|
+
))}
|
|
361
|
+
</div>
|
|
351
362
|
)}
|
|
352
363
|
</div>
|
|
353
364
|
|
|
@@ -357,89 +368,102 @@ export default function TagPage() {
|
|
|
357
368
|
totalItems={total}
|
|
358
369
|
onPageChange={setPage}
|
|
359
370
|
onPageSizeChange={setPageSize}
|
|
360
|
-
pageSizeOptions={[
|
|
371
|
+
pageSizeOptions={[...PAGE_SIZE_OPTIONS]}
|
|
361
372
|
/>
|
|
362
373
|
|
|
363
|
-
<
|
|
364
|
-
<
|
|
365
|
-
<
|
|
366
|
-
<
|
|
367
|
-
|
|
368
|
-
</DialogTitle>
|
|
369
|
-
<DialogDescription>
|
|
374
|
+
<Sheet open={isSheetOpen} onOpenChange={setIsSheetOpen}>
|
|
375
|
+
<SheetContent className="w-full overflow-y-auto sm:max-w-lg">
|
|
376
|
+
<SheetHeader>
|
|
377
|
+
<SheetTitle>{isNewTag ? t('createTag') : t('editTag')}</SheetTitle>
|
|
378
|
+
<SheetDescription>
|
|
370
379
|
{isNewTag ? t('createDescription') : t('editDescription')}
|
|
371
|
-
</
|
|
372
|
-
</
|
|
380
|
+
</SheetDescription>
|
|
381
|
+
</SheetHeader>
|
|
373
382
|
|
|
374
|
-
<
|
|
375
|
-
<
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
|
|
383
|
+
<Form {...form}>
|
|
384
|
+
<form
|
|
385
|
+
onSubmit={form.handleSubmit(handleSaveTag)}
|
|
386
|
+
className="mt-6 space-y-4 px-4"
|
|
387
|
+
>
|
|
388
|
+
<FormField
|
|
389
|
+
control={form.control}
|
|
390
|
+
name="slug"
|
|
391
|
+
render={({ field }) => (
|
|
392
|
+
<FormItem>
|
|
393
|
+
<FormLabel>{t('slug')}</FormLabel>
|
|
394
|
+
<FormControl>
|
|
395
|
+
<Input {...field} placeholder={t('slugPlaceholder')} />
|
|
396
|
+
</FormControl>
|
|
397
|
+
<FormMessage />
|
|
398
|
+
</FormItem>
|
|
399
|
+
)}
|
|
384
400
|
/>
|
|
385
|
-
</div>
|
|
386
401
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
402
|
+
<FormField
|
|
403
|
+
control={form.control}
|
|
404
|
+
name="color"
|
|
405
|
+
render={({ field }) => (
|
|
406
|
+
<FormItem>
|
|
407
|
+
<FormLabel>{t('color')}</FormLabel>
|
|
408
|
+
<FormControl>
|
|
409
|
+
<div className="flex gap-2">
|
|
410
|
+
<Input
|
|
411
|
+
type="color"
|
|
412
|
+
value={field.value}
|
|
413
|
+
onChange={(e) => field.onChange(e.target.value)}
|
|
414
|
+
className="h-10 w-20"
|
|
415
|
+
/>
|
|
416
|
+
<Input
|
|
417
|
+
value={field.value}
|
|
418
|
+
onChange={(e) => field.onChange(e.target.value)}
|
|
419
|
+
placeholder={t('colorPlaceholder')}
|
|
420
|
+
/>
|
|
421
|
+
</div>
|
|
422
|
+
</FormControl>
|
|
423
|
+
<FormMessage />
|
|
424
|
+
</FormItem>
|
|
425
|
+
)}
|
|
426
|
+
/>
|
|
408
427
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
428
|
+
<FormField
|
|
429
|
+
control={form.control}
|
|
430
|
+
name="status"
|
|
431
|
+
render={({ field }) => (
|
|
432
|
+
<FormItem>
|
|
433
|
+
<FormLabel>{t('status')}</FormLabel>
|
|
434
|
+
<FormControl>
|
|
435
|
+
<Select
|
|
436
|
+
value={field.value}
|
|
437
|
+
onValueChange={(value: 'active' | 'inactive') =>
|
|
438
|
+
field.onChange(value)
|
|
439
|
+
}
|
|
440
|
+
>
|
|
441
|
+
<SelectTrigger className="w-full">
|
|
442
|
+
<SelectValue />
|
|
443
|
+
</SelectTrigger>
|
|
444
|
+
<SelectContent>
|
|
445
|
+
<SelectItem value="active">{t('active')}</SelectItem>
|
|
446
|
+
<SelectItem value="inactive">
|
|
447
|
+
{t('inactive')}
|
|
448
|
+
</SelectItem>
|
|
449
|
+
</SelectContent>
|
|
450
|
+
</Select>
|
|
451
|
+
</FormControl>
|
|
452
|
+
<FormMessage />
|
|
453
|
+
</FormItem>
|
|
454
|
+
)}
|
|
455
|
+
/>
|
|
427
456
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
</Button>
|
|
440
|
-
</DialogFooter>
|
|
441
|
-
</DialogContent>
|
|
442
|
-
</Dialog>
|
|
443
|
-
</div>
|
|
457
|
+
<SheetFooter className="px-0">
|
|
458
|
+
<Button type="submit" className="w-full">
|
|
459
|
+
<Save className="mr-2 h-4 w-4" />
|
|
460
|
+
{t('save')}
|
|
461
|
+
</Button>
|
|
462
|
+
</SheetFooter>
|
|
463
|
+
</form>
|
|
464
|
+
</Form>
|
|
465
|
+
</SheetContent>
|
|
466
|
+
</Sheet>
|
|
467
|
+
</Page>
|
|
444
468
|
);
|
|
445
469
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"activeTags": "Active Tags",
|
|
8
8
|
"inactiveTags": "Inactive Tags",
|
|
9
9
|
"searchPlaceholder": "Search tags...",
|
|
10
|
+
"clearSearch": "Clear search",
|
|
10
11
|
"noTagsFound": "No tags found",
|
|
11
12
|
"adjustSearch": "Try adjusting your search",
|
|
12
13
|
"createNewTag": "Start by creating a new tag",
|
|
@@ -31,6 +32,10 @@
|
|
|
31
32
|
"successCreate": "Tag created successfully!",
|
|
32
33
|
"errorSave": "Error saving tag.",
|
|
33
34
|
"successDelete": "Tag deleted successfully!",
|
|
34
|
-
"errorDelete": "Error deleting tag."
|
|
35
|
+
"errorDelete": "Error deleting tag.",
|
|
36
|
+
"validation": {
|
|
37
|
+
"slugMin": "Slug must have at least 2 characters.",
|
|
38
|
+
"colorInvalid": "Enter a valid hex color (e.g. #000000)."
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"activeTags": "Tags Ativas",
|
|
8
8
|
"inactiveTags": "Tags Inativas",
|
|
9
9
|
"searchPlaceholder": "Buscar tags...",
|
|
10
|
+
"clearSearch": "Limpar busca",
|
|
10
11
|
"noTagsFound": "Nenhuma tag encontrada",
|
|
11
12
|
"adjustSearch": "Tente ajustar sua busca",
|
|
12
13
|
"createNewTag": "Comece criando uma nova tag",
|
|
@@ -31,6 +32,10 @@
|
|
|
31
32
|
"successCreate": "Tag criada com sucesso!",
|
|
32
33
|
"errorSave": "Erro ao salvar a tag.",
|
|
33
34
|
"successDelete": "Tag excluída com sucesso!",
|
|
34
|
-
"errorDelete": "Erro ao excluir a tag."
|
|
35
|
+
"errorDelete": "Erro ao excluir a tag.",
|
|
36
|
+
"validation": {
|
|
37
|
+
"slugMin": "O slug deve ter pelo menos 2 caracteres.",
|
|
38
|
+
"colorInvalid": "Informe uma cor hexadecimal válida (ex: #000000)."
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hed-hog/tag",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.285",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"@nestjs/mapped-types": "*",
|
|
12
12
|
"@hed-hog/api-prisma": "0.0.5",
|
|
13
13
|
"@hed-hog/api-pagination": "0.0.6",
|
|
14
|
-
"@hed-hog/core": "0.0.278",
|
|
15
14
|
"@hed-hog/api": "0.0.4",
|
|
16
|
-
"@hed-hog/api-locale": "0.0.13"
|
|
15
|
+
"@hed-hog/api-locale": "0.0.13",
|
|
16
|
+
"@hed-hog/core": "0.0.285"
|
|
17
17
|
},
|
|
18
18
|
"exports": {
|
|
19
19
|
".": {
|