@hed-hog/tag 0.0.279 → 0.0.286

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/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ ```markdown
1
2
  # @hed-hog/tag
2
3
 
3
4
  ## 1. Visão geral do módulo
@@ -350,3 +351,4 @@ Resposta:
350
351
  ---
351
352
 
352
353
  Este módulo é parte integrante do monorepo HedHog e deve ser utilizado conforme as regras de autenticação e autorização definidas.
354
+ ```
@@ -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
- Dialog,
24
- DialogContent,
25
- DialogDescription,
26
- DialogFooter,
27
- DialogHeader,
28
- DialogTitle,
29
- } from '@/components/ui/dialog';
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 { Edit, Save, Tag as TagIcon, Trash2, X } from 'lucide-react';
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,21 +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 [tags, setTags] = useState<Tag[]>([]);
63
- const [selectedTag, setSelectedTag] = useState<Partial<Tag> | null>(null);
64
- const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
65
- const [isNewTag, setIsNewTag] = useState(false);
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 { data: tagResult, refetch: refetchTag } = useQuery<
73
- PaginationResult<Tag>
74
- >({
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
+ >({
75
111
  queryKey: ['tag', debouncedSearch, page, pageSize],
76
112
  queryFn: async () => {
77
113
  const response = await request({
@@ -84,8 +120,8 @@ export default function TagPage() {
84
120
  });
85
121
  return response.data as PaginationResult<Tag>;
86
122
  },
87
- });
88
- const { data = [], total = 0 } = tagResult ?? {};
123
+ });
124
+ const { data = [], total = 0 } = tagResult ?? {};
89
125
 
90
126
  const { data: statsData, refetch: refetchStats } = useQuery<any>({
91
127
  queryKey: ['tag-stats'],
@@ -97,51 +133,47 @@ export default function TagPage() {
97
133
  },
98
134
  });
99
135
 
100
- useEffect(() => {
101
- if (data) {
102
- setTags(data);
103
- }
104
- }, [data]);
105
-
106
136
  const handleNewTag = (): void => {
107
- setSelectedTag({
137
+ form.reset({
108
138
  slug: '',
109
139
  color: '#000000',
110
140
  status: 'active',
111
141
  });
112
- setIsNewTag(true);
113
- setIsEditDialogOpen(true);
142
+ setEditingTagId(null);
143
+ setIsSheetOpen(true);
114
144
  };
115
145
 
116
146
  const handleEditTag = async (tag: Tag): Promise<void> => {
117
147
  try {
118
- const response = await request({
148
+ const response = await request<Tag>({
119
149
  url: `/tag/${tag.id}`,
120
150
  method: 'GET',
121
151
  });
122
152
 
123
- setSelectedTag(response.data);
124
- setIsNewTag(false);
125
- setIsEditDialogOpen(true);
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);
126
160
  } catch (error) {
127
161
  console.error(error);
128
162
  toast.error(t('errorLoading'));
129
163
  }
130
164
  };
131
165
 
132
- const handleSaveTag = async () => {
133
- if (!selectedTag) return;
134
-
166
+ const handleSaveTag = async (values: FormValues) => {
135
167
  const payload = {
136
- slug: selectedTag.slug,
137
- color: selectedTag.color,
138
- status: selectedTag.status,
168
+ slug: values.slug,
169
+ color: values.color,
170
+ status: values.status,
139
171
  };
140
172
 
141
173
  try {
142
- if (selectedTag.id) {
174
+ if (editingTagId) {
143
175
  await request({
144
- url: `/tag/${selectedTag.id}`,
176
+ url: `/tag/${editingTagId}`,
145
177
  method: 'PATCH',
146
178
  data: payload,
147
179
  });
@@ -155,7 +187,7 @@ export default function TagPage() {
155
187
  toast.success(t('successCreate'));
156
188
  }
157
189
 
158
- setIsEditDialogOpen(false);
190
+ setIsSheetOpen(false);
159
191
  await refetchTag();
160
192
  await refetchStats();
161
193
  } catch (error) {
@@ -181,20 +213,19 @@ export default function TagPage() {
181
213
 
182
214
  const handleSearchChange = (value: string): void => {
183
215
  setSearchTerm(value);
216
+ setPage(1);
184
217
  };
185
218
 
186
219
  useEffect(() => {
187
- refetchTag();
188
220
  refetchStats();
189
- }, [isEditDialogOpen, debouncedSearch, page, pageSize]);
221
+ }, [isSheetOpen, refetchStats]);
222
+
223
+ const isNewTag = editingTagId === null;
190
224
 
191
225
  return (
192
- <div className="flex flex-col h-screen px-4">
226
+ <Page>
193
227
  <PageHeader
194
- breadcrumbs={[
195
- { label: 'Home', href: '/' },
196
- { label: t('description') },
197
- ]}
228
+ breadcrumbs={[{ label: 'Home', href: '/' }, { label: t('title') }]}
198
229
  actions={[
199
230
  {
200
231
  label: t('newTag'),
@@ -206,142 +237,128 @@ export default function TagPage() {
206
237
  description={t('description')}
207
238
  />
208
239
 
209
- <div className="grid grid-cols-1 gap-4 md:grid-cols-3">
210
- <Card className="transition-shadow hover:shadow-md p-0">
211
- <CardContent className="p-4">
212
- <div className="flex items-center space-x-3">
213
- <div className="rounded-full bg-blue-100 p-2 dark:bg-blue-900">
214
- <TagIcon className="h-6 w-6 text-blue-600 dark:text-blue-400" />
215
- </div>
216
- <div>
217
- <p className="text-sm font-medium text-muted-foreground">
218
- {t('totalTags')}
219
- </p>
220
- <p className="text-2xl font-bold">{statsData?.total || 0}</p>
221
- </div>
222
- </div>
223
- </CardContent>
224
- </Card>
225
-
226
- <Card className="transition-shadow hover:shadow-md p-0">
227
- <CardContent className="p-4">
228
- <div className="flex items-center space-x-3">
229
- <div className="rounded-full bg-green-100 p-2 dark:bg-green-900">
230
- <TagIcon className="h-6 w-6 text-green-600 dark:text-green-400" />
231
- </div>
232
- <div>
233
- <p className="text-sm font-medium text-muted-foreground">
234
- {t('activeTags')}
235
- </p>
236
- <p className="text-2xl font-bold">{statsData?.active || 0}</p>
237
- </div>
238
- </div>
239
- </CardContent>
240
- </Card>
241
-
242
- <Card className="transition-shadow hover:shadow-md p-0">
243
- <CardContent className="p-4">
244
- <div className="flex items-center space-x-3">
245
- <div className="rounded-full bg-gray-100 p-2 dark:bg-gray-900">
246
- <TagIcon className="h-6 w-6 text-gray-600 dark:text-gray-400" />
247
- </div>
248
- <div>
249
- <p className="text-sm font-medium text-muted-foreground">
250
- {t('inactiveTags')}
251
- </p>
252
- <p className="text-2xl font-bold">{statsData?.inactive || 0}</p>
253
- </div>
254
- </div>
255
- </CardContent>
256
- </Card>
257
- </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
+ />
258
266
 
259
- <div className="mb-4 flex flex-col gap-4 md:flex-row mt-4">
260
- <SearchBar
261
- searchQuery={searchTerm}
262
- onSearchChange={handleSearchChange}
263
- onSearch={() => refetchTag()}
264
- placeholder={t('searchPlaceholder')}
265
- />
266
- </div>
267
+ <SearchBar
268
+ searchQuery={searchTerm}
269
+ onSearchChange={handleSearchChange}
270
+ onSearch={() => refetchTag()}
271
+ placeholder={t('searchPlaceholder')}
272
+ />
267
273
 
268
- <div className="space-y-3">
269
- {tags.length === 0 ? (
270
- <Card>
271
- <CardContent className="flex flex-col items-center justify-center p-12">
272
- <TagIcon className="mb-4 h-12 w-12 text-muted-foreground" />
273
- <p className="text-lg font-medium text-muted-foreground">
274
- {t('noTagsFound')}
275
- </p>
276
- <p className="text-sm text-muted-foreground">
277
- {searchTerm ? t('adjustSearch') : t('createNewTag')}
278
- </p>
279
- </CardContent>
280
- </Card>
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
+ />
281
290
  ) : (
282
- tags.map((tag) => (
283
- <Card
284
- key={tag.id}
285
- onDoubleClick={() => handleEditTag(tag)}
286
- className="cursor-pointer transition-shadow hover:shadow-md"
287
- >
288
- <CardContent className="flex items-center justify-between p-6">
289
- <div className="flex items-center space-x-4">
290
- <div
291
- className="h-10 w-10 rounded-md"
292
- style={{ backgroundColor: tag.color }}
293
- />
294
- <div>
295
- <div className="flex items-center gap-2">
296
- <h2 className="font-semibold text-lg">#{tag.slug}</h2>
297
- <Badge
298
- variant={
299
- tag.status === 'active' ? 'default' : 'secondary'
300
- }
301
- >
302
- {tag.status === 'active' ? t('active') : t('inactive')}
303
- </Badge>
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>
304
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>
305
357
  </div>
306
- </div>
307
- <div className="flex space-x-2">
308
- <Button
309
- variant="ghost"
310
- size="icon"
311
- onClick={() => handleEditTag(tag)}
312
- >
313
- <Edit className="h-4 w-4" />
314
- </Button>
315
- <AlertDialog>
316
- <AlertDialogTrigger asChild>
317
- <Button variant="ghost" size="icon">
318
- <Trash2 className="h-4 w-4 text-destructive" />
319
- </Button>
320
- </AlertDialogTrigger>
321
- <AlertDialogContent>
322
- <AlertDialogHeader>
323
- <AlertDialogTitle>
324
- {t('confirmDelete')}
325
- </AlertDialogTitle>
326
- <AlertDialogDescription>
327
- {t('deleteDescription')}
328
- </AlertDialogDescription>
329
- </AlertDialogHeader>
330
- <AlertDialogFooter>
331
- <AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
332
- <AlertDialogAction
333
- onClick={() => handleDeleteTag(tag.id)}
334
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
335
- >
336
- {t('delete')}
337
- </AlertDialogAction>
338
- </AlertDialogFooter>
339
- </AlertDialogContent>
340
- </AlertDialog>
341
- </div>
342
- </CardContent>
343
- </Card>
344
- ))
358
+ </CardContent>
359
+ </Card>
360
+ ))}
361
+ </div>
345
362
  )}
346
363
  </div>
347
364
 
@@ -351,89 +368,102 @@ export default function TagPage() {
351
368
  totalItems={total}
352
369
  onPageChange={setPage}
353
370
  onPageSizeChange={setPageSize}
354
- pageSizeOptions={[10, 20, 30, 40, 50]}
371
+ pageSizeOptions={[...PAGE_SIZE_OPTIONS]}
355
372
  />
356
373
 
357
- <Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
358
- <DialogContent className="max-h-[90vh] max-w-2xl overflow-y-auto">
359
- <DialogHeader>
360
- <DialogTitle>
361
- {isNewTag ? t('createTag') : t('editTag')}
362
- </DialogTitle>
363
- <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>
364
379
  {isNewTag ? t('createDescription') : t('editDescription')}
365
- </DialogDescription>
366
- </DialogHeader>
380
+ </SheetDescription>
381
+ </SheetHeader>
367
382
 
368
- <div className="space-y-4">
369
- <div className="space-y-2">
370
- <Label htmlFor="slug">{t('slug')}</Label>
371
- <Input
372
- id="slug"
373
- value={selectedTag?.slug || ''}
374
- onChange={(e) =>
375
- setSelectedTag({ ...selectedTag, slug: e.target.value })
376
- }
377
- placeholder={t('slugPlaceholder')}
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
+ )}
378
400
  />
379
- </div>
380
401
 
381
- <div className="space-y-2">
382
- <Label htmlFor="color">{t('color')}</Label>
383
- <div className="flex gap-2">
384
- <Input
385
- id="color"
386
- type="color"
387
- value={selectedTag?.color || '#000000'}
388
- onChange={(e) =>
389
- setSelectedTag({ ...selectedTag, color: e.target.value })
390
- }
391
- className="h-10 w-20"
392
- />
393
- <Input
394
- value={selectedTag?.color || '#000000'}
395
- onChange={(e) =>
396
- setSelectedTag({ ...selectedTag, color: e.target.value })
397
- }
398
- placeholder={t('colorPlaceholder')}
399
- />
400
- </div>
401
- </div>
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
+ />
402
427
 
403
- <div className="space-y-2">
404
- <Label htmlFor="status">{t('status')}</Label>
405
- <Select
406
- value={selectedTag?.status || 'active'}
407
- onValueChange={(value: 'active' | 'inactive') =>
408
- setSelectedTag({ ...selectedTag, status: value })
409
- }
410
- >
411
- <SelectTrigger className="w-full" id="status">
412
- <SelectValue />
413
- </SelectTrigger>
414
- <SelectContent>
415
- <SelectItem value="active">{t('active')}</SelectItem>
416
- <SelectItem value="inactive">{t('inactive')}</SelectItem>
417
- </SelectContent>
418
- </Select>
419
- </div>
420
- </div>
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
+ />
421
456
 
422
- <DialogFooter>
423
- <Button
424
- variant="outline"
425
- onClick={() => setIsEditDialogOpen(false)}
426
- >
427
- <X className="mr-2 h-4 w-4" />
428
- {t('cancel')}
429
- </Button>
430
- <Button onClick={handleSaveTag}>
431
- <Save className="mr-2 h-4 w-4" />
432
- {t('save')}
433
- </Button>
434
- </DialogFooter>
435
- </DialogContent>
436
- </Dialog>
437
- </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>
438
468
  );
439
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.279",
3
+ "version": "0.0.286",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -10,9 +10,9 @@
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
12
  "@hed-hog/api-prisma": "0.0.5",
13
- "@hed-hog/api-pagination": "0.0.6",
14
- "@hed-hog/core": "0.0.279",
13
+ "@hed-hog/core": "0.0.286",
15
14
  "@hed-hog/api-locale": "0.0.13",
15
+ "@hed-hog/api-pagination": "0.0.6",
16
16
  "@hed-hog/api": "0.0.4"
17
17
  },
18
18
  "exports": {