@hed-hog/category 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.
- package/README.md +7 -4
- package/hedhog/frontend/app/page.tsx.ejs +502 -459
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -33,7 +33,8 @@ O módulo `@hed-hog/category` é responsável pela gestão de categorias no sist
|
|
|
33
33
|
#### GET `/category/root`
|
|
34
34
|
|
|
35
35
|
- **Autenticação:** Necessária (roles: admin, admin-category)
|
|
36
|
-
- **Query:**
|
|
36
|
+
- **Query:**
|
|
37
|
+
- `locale` (string, obrigatório) — código do idioma para as localizações.
|
|
37
38
|
- **Resposta:** Lista de categorias raiz ativas com traduções no idioma solicitado.
|
|
38
39
|
- **Erros comuns:**
|
|
39
40
|
- 400 Bad Request: Locale inválido ou não encontrado.
|
|
@@ -57,7 +58,8 @@ O módulo `@hed-hog/category` é responsável pela gestão de categorias no sist
|
|
|
57
58
|
- **Autenticação:** Necessária (roles: admin, admin-category)
|
|
58
59
|
- **Parâmetros:**
|
|
59
60
|
- `id` (number, obrigatório): ID da categoria.
|
|
60
|
-
- **Query:**
|
|
61
|
+
- **Query:**
|
|
62
|
+
- `locale` (string, obrigatório) — código do idioma para as localizações.
|
|
61
63
|
- **Resposta:** Objeto categoria com localizações.
|
|
62
64
|
- **Erros comuns:**
|
|
63
65
|
- 404 Not Found: Categoria não encontrada.
|
|
@@ -67,7 +69,8 @@ O módulo `@hed-hog/category` é responsável pela gestão de categorias no sist
|
|
|
67
69
|
- **Autenticação:** Necessária (roles: admin, admin-category)
|
|
68
70
|
- **Parâmetros:**
|
|
69
71
|
- `categoryId` (string, obrigatório): ID da categoria pai.
|
|
70
|
-
- **Query:**
|
|
72
|
+
- **Query:**
|
|
73
|
+
- `locale` (string, obrigatório) — código do idioma para as localizações.
|
|
71
74
|
- **Resposta:** Lista de categorias filhas com localizações.
|
|
72
75
|
- **Erros comuns:**
|
|
73
76
|
- 400 Bad Request: Locale inválido.
|
|
@@ -79,7 +82,7 @@ O módulo `@hed-hog/category` é responsável pela gestão de categorias no sist
|
|
|
79
82
|
- **Query:**
|
|
80
83
|
- `locale` (string, obrigatório) — código do idioma para as localizações.
|
|
81
84
|
- `status` (string, opcional): Filtra por status (`active`, `inactive`, `all`).
|
|
82
|
-
- `parent` (string, opcional): Filtra por categoria pai (`slug
|
|
85
|
+
- `parent` (string, opcional): Filtra por categoria pai (`slug`, `root` ou `all`).
|
|
83
86
|
- Parâmetros de paginação via cabeçalho ou query (padrão do módulo de paginação).
|
|
84
87
|
- **Resposta:** Objeto paginado com categorias e localizações.
|
|
85
88
|
- **Erros comuns:**
|
|
@@ -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,8 +38,17 @@ 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';
|
|
51
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
41
52
|
import * as TablerIcons from '@tabler/icons-react';
|
|
42
53
|
import {
|
|
43
54
|
Edit,
|
|
@@ -48,11 +59,12 @@ import {
|
|
|
48
59
|
Save,
|
|
49
60
|
Tag,
|
|
50
61
|
Trash2,
|
|
51
|
-
X,
|
|
52
62
|
} from 'lucide-react';
|
|
53
63
|
import { useTranslations } from 'next-intl';
|
|
54
64
|
import { useEffect, useState } from 'react';
|
|
65
|
+
import { useForm } from 'react-hook-form';
|
|
55
66
|
import { toast } from 'sonner';
|
|
67
|
+
import { z } from 'zod';
|
|
56
68
|
|
|
57
69
|
type PaginationResult<T> = {
|
|
58
70
|
data: T[];
|
|
@@ -83,10 +95,23 @@ type Locale = {
|
|
|
83
95
|
name: string;
|
|
84
96
|
};
|
|
85
97
|
|
|
98
|
+
type CategoryFormValues = {
|
|
99
|
+
name: string;
|
|
100
|
+
slug: string;
|
|
101
|
+
color: string;
|
|
102
|
+
icon: string;
|
|
103
|
+
category_id: string;
|
|
104
|
+
status: 'active' | 'inactive';
|
|
105
|
+
};
|
|
106
|
+
|
|
86
107
|
export default function CategoryPage() {
|
|
87
108
|
const t = useTranslations('category.Category');
|
|
88
|
-
const [
|
|
89
|
-
|
|
109
|
+
const [editingCategoryId, setEditingCategoryId] = useState<number | null>(
|
|
110
|
+
null
|
|
111
|
+
);
|
|
112
|
+
const [localeData, setLocaleData] = useState<
|
|
113
|
+
Record<string, { name: string }>
|
|
114
|
+
>({});
|
|
90
115
|
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
|
|
91
116
|
const [isNewCategory, setIsNewCategory] = useState(false);
|
|
92
117
|
const [searchTerm, setSearchTerm] = useState('');
|
|
@@ -99,6 +124,28 @@ export default function CategoryPage() {
|
|
|
99
124
|
const [fullLocales, setFullLocales] = useState<any[]>([]);
|
|
100
125
|
const { request, locales, currentLocaleCode } = useApp();
|
|
101
126
|
|
|
127
|
+
const categorySchema = z.object({
|
|
128
|
+
name: z.string().trim().min(1, t('nameRequired')),
|
|
129
|
+
slug: z.string().trim().min(1, t('slugRequired')),
|
|
130
|
+
color: z.string().trim().min(1, t('color')),
|
|
131
|
+
icon: z.string().trim().optional(),
|
|
132
|
+
category_id: z.string(),
|
|
133
|
+
status: z.enum(['active', 'inactive']),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const form = useForm<CategoryFormValues>({
|
|
137
|
+
resolver: zodResolver(categorySchema),
|
|
138
|
+
mode: 'onChange',
|
|
139
|
+
defaultValues: {
|
|
140
|
+
name: '',
|
|
141
|
+
slug: '',
|
|
142
|
+
color: '#000000',
|
|
143
|
+
icon: '',
|
|
144
|
+
category_id: 'none',
|
|
145
|
+
status: 'active',
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
102
149
|
// Buscar locales completos com id
|
|
103
150
|
useEffect(() => {
|
|
104
151
|
const fetchLocales = async () => {
|
|
@@ -123,10 +170,9 @@ export default function CategoryPage() {
|
|
|
123
170
|
}
|
|
124
171
|
}, [currentLocaleCode]);
|
|
125
172
|
|
|
126
|
-
const {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
} = useQuery<PaginationResult<Category>>({
|
|
173
|
+
const { data: categoriesResult, refetch: refetchCategories } = useQuery<
|
|
174
|
+
PaginationResult<Category>
|
|
175
|
+
>({
|
|
130
176
|
queryKey: [
|
|
131
177
|
'categories',
|
|
132
178
|
debouncedSearch,
|
|
@@ -148,13 +194,8 @@ export default function CategoryPage() {
|
|
|
148
194
|
});
|
|
149
195
|
return response.data as PaginationResult<Category>;
|
|
150
196
|
},
|
|
151
|
-
initialData: {
|
|
152
|
-
data: [],
|
|
153
|
-
total: 0,
|
|
154
|
-
page: 1,
|
|
155
|
-
pageSize: 10,
|
|
156
|
-
},
|
|
157
197
|
});
|
|
198
|
+
const { data: categories = [], total = 0 } = categoriesResult ?? {};
|
|
158
199
|
|
|
159
200
|
const { data: rootCategories = [] } = useQuery<Category[]>({
|
|
160
201
|
queryKey: ['root-categories'],
|
|
@@ -164,7 +205,6 @@ export default function CategoryPage() {
|
|
|
164
205
|
});
|
|
165
206
|
return (response.data || []) as Category[];
|
|
166
207
|
},
|
|
167
|
-
initialData: [],
|
|
168
208
|
enabled: true,
|
|
169
209
|
staleTime: 0,
|
|
170
210
|
refetchOnMount: true,
|
|
@@ -180,12 +220,6 @@ export default function CategoryPage() {
|
|
|
180
220
|
},
|
|
181
221
|
});
|
|
182
222
|
|
|
183
|
-
useEffect(() => {
|
|
184
|
-
if (data) {
|
|
185
|
-
setCategories(data);
|
|
186
|
-
}
|
|
187
|
-
}, [data]);
|
|
188
|
-
|
|
189
223
|
const renderIcon = (iconName?: string) => {
|
|
190
224
|
const toPascalCase = (str: string) =>
|
|
191
225
|
str.replace(/(^\w|-\w)/g, (match) =>
|
|
@@ -209,22 +243,26 @@ export default function CategoryPage() {
|
|
|
209
243
|
};
|
|
210
244
|
|
|
211
245
|
const handleNewCategory = (): void => {
|
|
212
|
-
const
|
|
213
|
-
slug: '',
|
|
214
|
-
category_id: null,
|
|
215
|
-
color: '#000000',
|
|
216
|
-
icon: '',
|
|
217
|
-
status: 'active',
|
|
218
|
-
locale: {},
|
|
219
|
-
};
|
|
220
|
-
|
|
246
|
+
const nextLocaleData: Record<string, { name: string }> = {};
|
|
221
247
|
locales.forEach((locale: Locale) => {
|
|
222
|
-
|
|
248
|
+
nextLocaleData[locale.code] = {
|
|
223
249
|
name: '',
|
|
224
250
|
};
|
|
225
251
|
});
|
|
226
252
|
|
|
227
|
-
|
|
253
|
+
const initialLocale = currentLocaleCode || locales[0]?.code || '';
|
|
254
|
+
|
|
255
|
+
setEditingCategoryId(null);
|
|
256
|
+
setLocaleData(nextLocaleData);
|
|
257
|
+
setSelectedLocale(initialLocale);
|
|
258
|
+
form.reset({
|
|
259
|
+
name: '',
|
|
260
|
+
slug: '',
|
|
261
|
+
color: '#000000',
|
|
262
|
+
icon: '',
|
|
263
|
+
category_id: 'none',
|
|
264
|
+
status: 'active',
|
|
265
|
+
});
|
|
228
266
|
setIsNewCategory(true);
|
|
229
267
|
setIsEditDialogOpen(true);
|
|
230
268
|
};
|
|
@@ -268,9 +306,21 @@ export default function CategoryPage() {
|
|
|
268
306
|
}
|
|
269
307
|
});
|
|
270
308
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
309
|
+
const initialLocale =
|
|
310
|
+
selectedLocale || currentLocaleCode || locales[0]?.code || '';
|
|
311
|
+
|
|
312
|
+
setEditingCategoryId(Number(categoryData.id));
|
|
313
|
+
setLocaleData(localeData);
|
|
314
|
+
setSelectedLocale(initialLocale);
|
|
315
|
+
form.reset({
|
|
316
|
+
name: localeData[initialLocale]?.name || '',
|
|
317
|
+
slug: categoryData.slug || '',
|
|
318
|
+
color: categoryData.color || '#000000',
|
|
319
|
+
icon: categoryData.icon || '',
|
|
320
|
+
category_id: categoryData.category_id
|
|
321
|
+
? String(categoryData.category_id)
|
|
322
|
+
: 'none',
|
|
323
|
+
status: categoryData.status === 'inactive' ? 'inactive' : 'active',
|
|
274
324
|
});
|
|
275
325
|
setIsNewCategory(false);
|
|
276
326
|
setIsEditDialogOpen(true);
|
|
@@ -280,22 +330,28 @@ export default function CategoryPage() {
|
|
|
280
330
|
}
|
|
281
331
|
};
|
|
282
332
|
|
|
283
|
-
const handleSaveCategory = async () => {
|
|
284
|
-
|
|
333
|
+
const handleSaveCategory = form.handleSubmit(async (values) => {
|
|
334
|
+
const mergedLocaleData = {
|
|
335
|
+
...localeData,
|
|
336
|
+
[selectedLocale]: {
|
|
337
|
+
name: values.name,
|
|
338
|
+
},
|
|
339
|
+
};
|
|
285
340
|
|
|
286
341
|
const payload = {
|
|
287
|
-
locale:
|
|
288
|
-
slug:
|
|
289
|
-
category_id:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
342
|
+
locale: mergedLocaleData,
|
|
343
|
+
slug: values.slug,
|
|
344
|
+
category_id:
|
|
345
|
+
values.category_id === 'none' ? null : Number(values.category_id),
|
|
346
|
+
color: values.color,
|
|
347
|
+
icon: values.icon,
|
|
348
|
+
status: values.status,
|
|
293
349
|
};
|
|
294
350
|
|
|
295
351
|
try {
|
|
296
|
-
if (
|
|
352
|
+
if (editingCategoryId) {
|
|
297
353
|
await request({
|
|
298
|
-
url: `/category/${
|
|
354
|
+
url: `/category/${editingCategoryId}`,
|
|
299
355
|
method: 'PATCH',
|
|
300
356
|
data: payload,
|
|
301
357
|
});
|
|
@@ -310,13 +366,16 @@ export default function CategoryPage() {
|
|
|
310
366
|
}
|
|
311
367
|
|
|
312
368
|
setIsEditDialogOpen(false);
|
|
369
|
+
setEditingCategoryId(null);
|
|
370
|
+
setLocaleData({});
|
|
371
|
+
form.reset();
|
|
313
372
|
await refetchCategories();
|
|
314
373
|
await refetchStats();
|
|
315
374
|
} catch (error) {
|
|
316
375
|
console.error(error);
|
|
317
376
|
toast.error(t('errorSave'));
|
|
318
377
|
}
|
|
319
|
-
};
|
|
378
|
+
});
|
|
320
379
|
|
|
321
380
|
const handleDeleteCategory = async (categoryId: number): Promise<void> => {
|
|
322
381
|
try {
|
|
@@ -335,19 +394,16 @@ export default function CategoryPage() {
|
|
|
335
394
|
|
|
336
395
|
const handleSearchChange = (value: string): void => {
|
|
337
396
|
setSearchTerm(value);
|
|
397
|
+
setPage(1);
|
|
338
398
|
};
|
|
339
399
|
|
|
340
400
|
useEffect(() => {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
pageSize,
|
|
348
|
-
statusFilter,
|
|
349
|
-
parentFilter,
|
|
350
|
-
]);
|
|
401
|
+
if (!isEditDialogOpen || !selectedLocale) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
form.setValue('name', localeData[selectedLocale]?.name || '');
|
|
406
|
+
}, [form, isEditDialogOpen, localeData, selectedLocale]);
|
|
351
407
|
|
|
352
408
|
const getStatusBadge = (status: string) => {
|
|
353
409
|
return status === 'active' ? (
|
|
@@ -359,13 +415,41 @@ export default function CategoryPage() {
|
|
|
359
415
|
);
|
|
360
416
|
};
|
|
361
417
|
|
|
418
|
+
const statsCards = [
|
|
419
|
+
{
|
|
420
|
+
title: t('totalCategories'),
|
|
421
|
+
value: statsData?.total || 0,
|
|
422
|
+
icon: <Layers className="h-5 w-5" />,
|
|
423
|
+
iconBgColor: 'bg-blue-100',
|
|
424
|
+
iconColor: 'text-blue-600',
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
title: t('actives'),
|
|
428
|
+
value: statsData?.totalActive || 0,
|
|
429
|
+
icon: <Tag className="h-5 w-5" />,
|
|
430
|
+
iconBgColor: 'bg-green-100',
|
|
431
|
+
iconColor: 'text-green-600',
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
title: t('inactives'),
|
|
435
|
+
value: statsData?.totalInactive || 0,
|
|
436
|
+
icon: <Tag className="h-5 w-5" />,
|
|
437
|
+
iconBgColor: 'bg-orange-100',
|
|
438
|
+
iconColor: 'text-orange-600',
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
title: t('root'),
|
|
442
|
+
value: statsData?.totalRoot || 0,
|
|
443
|
+
icon: <FolderTree className="h-5 w-5" />,
|
|
444
|
+
iconBgColor: 'bg-purple-100',
|
|
445
|
+
iconColor: 'text-purple-600',
|
|
446
|
+
},
|
|
447
|
+
];
|
|
448
|
+
|
|
362
449
|
return (
|
|
363
|
-
<
|
|
450
|
+
<Page>
|
|
364
451
|
<PageHeader
|
|
365
|
-
breadcrumbs={[
|
|
366
|
-
{ label: 'Home', href: '/' },
|
|
367
|
-
{ label: t('description') },
|
|
368
|
-
]}
|
|
452
|
+
breadcrumbs={[{ label: 'Home', href: '/' }, { label: t('title') }]}
|
|
369
453
|
actions={[
|
|
370
454
|
{
|
|
371
455
|
label: t('newCategory'),
|
|
@@ -377,235 +461,163 @@ export default function CategoryPage() {
|
|
|
377
461
|
description={t('description')}
|
|
378
462
|
/>
|
|
379
463
|
|
|
380
|
-
<
|
|
381
|
-
<Card className="transition-shadow hover:shadow-md p-0">
|
|
382
|
-
<CardContent className="p-4">
|
|
383
|
-
<div className="flex items-center space-x-3">
|
|
384
|
-
<div className="rounded-full bg-blue-100 p-2 dark:bg-blue-900">
|
|
385
|
-
<Layers className="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
|
386
|
-
</div>
|
|
387
|
-
<div>
|
|
388
|
-
<p className="text-sm font-medium text-muted-foreground">
|
|
389
|
-
{t('totalCategories')}
|
|
390
|
-
</p>
|
|
391
|
-
<p className="text-2xl font-bold">{statsData?.total || 0}</p>
|
|
392
|
-
</div>
|
|
393
|
-
</div>
|
|
394
|
-
</CardContent>
|
|
395
|
-
</Card>
|
|
396
|
-
|
|
397
|
-
<Card className="transition-shadow hover:shadow-md p-0">
|
|
398
|
-
<CardContent className="p-4">
|
|
399
|
-
<div className="flex items-center space-x-3">
|
|
400
|
-
<div className="rounded-full bg-green-100 p-2 dark:bg-green-900">
|
|
401
|
-
<Tag className="h-6 w-6 text-green-600 dark:text-green-400" />
|
|
402
|
-
</div>
|
|
403
|
-
<div>
|
|
404
|
-
<p className="text-sm font-medium text-muted-foreground">
|
|
405
|
-
{t('actives')}
|
|
406
|
-
</p>
|
|
407
|
-
<p className="text-2xl font-bold">
|
|
408
|
-
{statsData?.totalActive || 0}
|
|
409
|
-
</p>
|
|
410
|
-
</div>
|
|
411
|
-
</div>
|
|
412
|
-
</CardContent>
|
|
413
|
-
</Card>
|
|
414
|
-
|
|
415
|
-
<Card className="transition-shadow hover:shadow-md p-0">
|
|
416
|
-
<CardContent className="p-4">
|
|
417
|
-
<div className="flex items-center space-x-3">
|
|
418
|
-
<div className="rounded-full bg-orange-100 p-2 dark:bg-orange-900">
|
|
419
|
-
<Tag className="h-6 w-6 text-orange-600 dark:text-orange-400" />
|
|
420
|
-
</div>
|
|
421
|
-
<div>
|
|
422
|
-
<p className="text-sm font-medium text-muted-foreground">
|
|
423
|
-
{t('inactives')}
|
|
424
|
-
</p>
|
|
425
|
-
<p className="text-2xl font-bold">
|
|
426
|
-
{statsData?.totalInactive || 0}
|
|
427
|
-
</p>
|
|
428
|
-
</div>
|
|
429
|
-
</div>
|
|
430
|
-
</CardContent>
|
|
431
|
-
</Card>
|
|
432
|
-
|
|
433
|
-
<Card className="transition-shadow hover:shadow-md p-0">
|
|
434
|
-
<CardContent className="p-4">
|
|
435
|
-
<div className="flex items-center space-x-3">
|
|
436
|
-
<div className="rounded-full bg-purple-100 p-2 dark:bg-purple-900">
|
|
437
|
-
<FolderTree className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
|
438
|
-
</div>
|
|
439
|
-
<div>
|
|
440
|
-
<p className="text-sm font-medium text-muted-foreground">
|
|
441
|
-
{t('root')}
|
|
442
|
-
</p>
|
|
443
|
-
<p className="text-2xl font-bold">
|
|
444
|
-
{statsData?.totalRoot || 0}
|
|
445
|
-
</p>
|
|
446
|
-
</div>
|
|
447
|
-
</div>
|
|
448
|
-
</CardContent>
|
|
449
|
-
</Card>
|
|
450
|
-
</div>
|
|
451
|
-
|
|
452
|
-
<div className="flex flex-col gap-4 sm:flex-row my-4">
|
|
453
|
-
<SearchBar
|
|
454
|
-
searchQuery={searchTerm}
|
|
455
|
-
onSearchChange={handleSearchChange}
|
|
456
|
-
onSearch={() => refetchCategories()}
|
|
457
|
-
placeholder={t('searchPlaceholder')}
|
|
458
|
-
/>
|
|
464
|
+
<StatsCards stats={statsCards} />
|
|
459
465
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
466
|
+
<SearchBar
|
|
467
|
+
searchQuery={searchTerm}
|
|
468
|
+
onSearchChange={handleSearchChange}
|
|
469
|
+
onSearch={() => setPage(1)}
|
|
470
|
+
placeholder={t('searchPlaceholder')}
|
|
471
|
+
controls={[
|
|
472
|
+
{
|
|
473
|
+
id: 'status-filter',
|
|
474
|
+
type: 'select',
|
|
475
|
+
value: statusFilter,
|
|
476
|
+
onChange: setStatusFilter,
|
|
477
|
+
placeholder: t('status'),
|
|
478
|
+
options: [
|
|
479
|
+
{ value: 'all', label: t('all') },
|
|
480
|
+
{ value: 'active', label: t('actives') },
|
|
481
|
+
{ value: 'inactive', label: t('inactives') },
|
|
482
|
+
],
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
id: 'parent-filter',
|
|
486
|
+
type: 'select',
|
|
487
|
+
value: parentFilter,
|
|
488
|
+
onChange: setParentFilter,
|
|
489
|
+
placeholder: t('hierarchy'),
|
|
490
|
+
options: [
|
|
491
|
+
{ value: 'all', label: t('allHierarchy') },
|
|
492
|
+
{ value: 'root', label: t('rootHierarchy') },
|
|
493
|
+
],
|
|
494
|
+
},
|
|
495
|
+
]}
|
|
496
|
+
/>
|
|
480
497
|
|
|
481
498
|
<div className="space-y-4">
|
|
482
499
|
{categories.length > 0 ? (
|
|
483
|
-
<div className="
|
|
500
|
+
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
|
484
501
|
{categories.map((category) => (
|
|
485
502
|
<Card
|
|
486
503
|
key={category.id}
|
|
487
504
|
onDoubleClick={() => handleEditCategory(category)}
|
|
488
505
|
className="cursor-pointer transition-all duration-200 hover:border-primary/20 hover:shadow-md"
|
|
489
506
|
>
|
|
490
|
-
<CardContent className="p-
|
|
491
|
-
<div className="flex items-start justify-between gap-
|
|
492
|
-
<div className="flex-
|
|
493
|
-
<div
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
>
|
|
502
|
-
|
|
503
|
-
{renderIcon(category.icon)}
|
|
504
|
-
</div>
|
|
507
|
+
<CardContent className="p-4">
|
|
508
|
+
<div className="flex items-start justify-between gap-3">
|
|
509
|
+
<div className="flex min-w-0 items-start gap-3">
|
|
510
|
+
<div
|
|
511
|
+
className="mt-0.5 rounded-full p-2"
|
|
512
|
+
style={{
|
|
513
|
+
backgroundColor: category.color
|
|
514
|
+
? `${category.color}20`
|
|
515
|
+
: '#00000020',
|
|
516
|
+
}}
|
|
517
|
+
>
|
|
518
|
+
<div style={{ color: category.color || '#000000' }}>
|
|
519
|
+
{renderIcon(category.icon)}
|
|
505
520
|
</div>
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
/>
|
|
526
|
-
{category.color}
|
|
527
|
-
</span>
|
|
528
|
-
)}
|
|
529
|
-
{category.icon && (
|
|
530
|
-
<span>
|
|
531
|
-
<strong>{t('icon')}:</strong> {category.icon}
|
|
521
|
+
</div>
|
|
522
|
+
<div className="min-w-0 space-y-2">
|
|
523
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
524
|
+
<h3 className="truncate text-base font-semibold leading-tight">
|
|
525
|
+
{category.name}
|
|
526
|
+
</h3>
|
|
527
|
+
{getStatusBadge(category.status)}
|
|
528
|
+
</div>
|
|
529
|
+
<div className="space-y-1 text-xs text-muted-foreground">
|
|
530
|
+
<p className="truncate">
|
|
531
|
+
<span className="font-medium text-foreground">
|
|
532
|
+
{t('slug')}:
|
|
533
|
+
</span>{' '}
|
|
534
|
+
{category.slug}
|
|
535
|
+
</p>
|
|
536
|
+
{category.color && (
|
|
537
|
+
<p className="flex items-center gap-2">
|
|
538
|
+
<span className="font-medium text-foreground">
|
|
539
|
+
{t('color')}:
|
|
532
540
|
</span>
|
|
533
|
-
|
|
534
|
-
|
|
541
|
+
<span
|
|
542
|
+
className="inline-block h-3.5 w-3.5 rounded border"
|
|
543
|
+
style={{
|
|
544
|
+
backgroundColor: category.color,
|
|
545
|
+
}}
|
|
546
|
+
/>
|
|
547
|
+
<span className="truncate">{category.color}</span>
|
|
548
|
+
</p>
|
|
549
|
+
)}
|
|
550
|
+
{category.icon && (
|
|
551
|
+
<p className="truncate">
|
|
552
|
+
<span className="font-medium text-foreground">
|
|
553
|
+
{t('icon')}:
|
|
554
|
+
</span>{' '}
|
|
555
|
+
{category.icon}
|
|
556
|
+
</p>
|
|
557
|
+
)}
|
|
535
558
|
</div>
|
|
536
559
|
</div>
|
|
537
560
|
</div>
|
|
561
|
+
</div>
|
|
538
562
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
563
|
+
<div className="mt-4 flex items-center justify-end gap-2">
|
|
564
|
+
<Button
|
|
565
|
+
variant="outline"
|
|
566
|
+
size="sm"
|
|
567
|
+
onClick={() => handleEditCategory(category)}
|
|
568
|
+
className="transition-colors hover:border-blue-200 hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-950"
|
|
569
|
+
>
|
|
570
|
+
<Edit className="mr-1 h-4 w-4" />
|
|
571
|
+
{t('edit')}
|
|
572
|
+
</Button>
|
|
573
|
+
|
|
574
|
+
<AlertDialog>
|
|
575
|
+
<AlertDialogTrigger asChild>
|
|
576
|
+
<Button
|
|
577
|
+
variant="outline"
|
|
578
|
+
size="sm"
|
|
579
|
+
className="bg-transparent transition-colors hover:border-red-200 hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-950"
|
|
580
|
+
>
|
|
581
|
+
<Trash2 className="mr-1 h-4 w-4" />
|
|
582
|
+
{t('delete')}
|
|
583
|
+
</Button>
|
|
584
|
+
</AlertDialogTrigger>
|
|
585
|
+
<AlertDialogContent>
|
|
586
|
+
<AlertDialogHeader>
|
|
587
|
+
<AlertDialogTitle>
|
|
588
|
+
{t('confirmDelete')}
|
|
589
|
+
</AlertDialogTitle>
|
|
590
|
+
<AlertDialogDescription>
|
|
591
|
+
{t('deleteDescription')}
|
|
592
|
+
</AlertDialogDescription>
|
|
593
|
+
</AlertDialogHeader>
|
|
594
|
+
<AlertDialogFooter>
|
|
595
|
+
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
|
|
596
|
+
<AlertDialogAction
|
|
597
|
+
onClick={() =>
|
|
598
|
+
handleDeleteCategory(Number(category.category_id))
|
|
599
|
+
}
|
|
600
|
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
556
601
|
>
|
|
557
|
-
<Trash2 className="mr-1 h-4 w-4" />
|
|
558
602
|
{t('delete')}
|
|
559
|
-
</
|
|
560
|
-
</
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
<AlertDialogTitle>
|
|
564
|
-
{t('confirmDelete')}
|
|
565
|
-
</AlertDialogTitle>
|
|
566
|
-
<AlertDialogDescription>
|
|
567
|
-
{t('deleteDescription')}
|
|
568
|
-
</AlertDialogDescription>
|
|
569
|
-
</AlertDialogHeader>
|
|
570
|
-
<AlertDialogFooter>
|
|
571
|
-
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
|
|
572
|
-
<AlertDialogAction
|
|
573
|
-
onClick={() =>
|
|
574
|
-
handleDeleteCategory(
|
|
575
|
-
Number(category.category_id)
|
|
576
|
-
)
|
|
577
|
-
}
|
|
578
|
-
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
579
|
-
>
|
|
580
|
-
{t('delete')}
|
|
581
|
-
</AlertDialogAction>
|
|
582
|
-
</AlertDialogFooter>
|
|
583
|
-
</AlertDialogContent>
|
|
584
|
-
</AlertDialog>
|
|
585
|
-
</div>
|
|
603
|
+
</AlertDialogAction>
|
|
604
|
+
</AlertDialogFooter>
|
|
605
|
+
</AlertDialogContent>
|
|
606
|
+
</AlertDialog>
|
|
586
607
|
</div>
|
|
587
608
|
</CardContent>
|
|
588
609
|
</Card>
|
|
589
610
|
))}
|
|
590
611
|
</div>
|
|
591
612
|
) : (
|
|
592
|
-
<
|
|
593
|
-
<
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
<p className="text-muted-foreground">{t('adjustFilters')}</p>
|
|
601
|
-
</div>
|
|
602
|
-
<Button onClick={handleNewCategory}>
|
|
603
|
-
<Plus className="mr-2 h-4 w-4" />
|
|
604
|
-
{t('createFirstCategory')}
|
|
605
|
-
</Button>
|
|
606
|
-
</div>
|
|
607
|
-
</CardContent>
|
|
608
|
-
</Card>
|
|
613
|
+
<EmptyState
|
|
614
|
+
icon={<Layers className="h-12 w-12" />}
|
|
615
|
+
title={t('noCategoriesFound')}
|
|
616
|
+
description={t('adjustFilters')}
|
|
617
|
+
actionLabel={t('createFirstCategory')}
|
|
618
|
+
actionIcon={<Plus className="mr-2 h-4 w-4" />}
|
|
619
|
+
onAction={handleNewCategory}
|
|
620
|
+
/>
|
|
609
621
|
)}
|
|
610
622
|
|
|
611
623
|
<PaginationFooter
|
|
@@ -613,42 +625,77 @@ export default function CategoryPage() {
|
|
|
613
625
|
pageSize={pageSize}
|
|
614
626
|
totalItems={total}
|
|
615
627
|
onPageChange={setPage}
|
|
616
|
-
onPageSizeChange={
|
|
628
|
+
onPageSizeChange={(nextPageSize) => {
|
|
629
|
+
setPageSize(nextPageSize);
|
|
630
|
+
setPage(1);
|
|
631
|
+
}}
|
|
617
632
|
pageSizeOptions={[10, 20, 30, 40, 50]}
|
|
618
633
|
/>
|
|
619
634
|
</div>
|
|
620
635
|
|
|
621
|
-
<
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
636
|
+
<Sheet
|
|
637
|
+
open={isEditDialogOpen}
|
|
638
|
+
onOpenChange={(open) => {
|
|
639
|
+
setIsEditDialogOpen(open);
|
|
640
|
+
if (!open) {
|
|
641
|
+
setIsNewCategory(false);
|
|
642
|
+
setEditingCategoryId(null);
|
|
643
|
+
setLocaleData({});
|
|
644
|
+
form.reset();
|
|
645
|
+
}
|
|
646
|
+
}}
|
|
647
|
+
>
|
|
648
|
+
<SheetContent className="w-full overflow-y-auto sm:max-w-2xl">
|
|
649
|
+
<SheetHeader>
|
|
650
|
+
<SheetTitle className="flex items-center space-x-2">
|
|
625
651
|
<Edit className="h-5 w-5" />
|
|
626
652
|
<span>
|
|
627
653
|
{isNewCategory ? t('newCategoryTitle') : t('editCategory')}
|
|
628
654
|
</span>
|
|
629
|
-
</
|
|
630
|
-
<
|
|
655
|
+
</SheetTitle>
|
|
656
|
+
<SheetDescription>
|
|
631
657
|
{isNewCategory ? t('createDescription') : t('editDescription')}
|
|
632
|
-
</
|
|
633
|
-
</
|
|
634
|
-
|
|
635
|
-
{
|
|
636
|
-
<
|
|
637
|
-
<
|
|
638
|
-
<
|
|
639
|
-
htmlFor="locale-select"
|
|
640
|
-
className="flex items-center gap-2"
|
|
641
|
-
>
|
|
658
|
+
</SheetDescription>
|
|
659
|
+
</SheetHeader>
|
|
660
|
+
|
|
661
|
+
<Form {...form}>
|
|
662
|
+
<form onSubmit={handleSaveCategory} className="space-y-4 px-4">
|
|
663
|
+
<FormItem>
|
|
664
|
+
<FormLabel className="flex items-center gap-2">
|
|
642
665
|
<Globe className="h-4 w-4" />
|
|
643
666
|
{t('language')}
|
|
644
|
-
</
|
|
667
|
+
</FormLabel>
|
|
645
668
|
<Select
|
|
646
669
|
value={selectedLocale}
|
|
647
|
-
onValueChange={
|
|
670
|
+
onValueChange={(nextLocale) => {
|
|
671
|
+
const currentName = form.getValues('name');
|
|
672
|
+
const currentLocale = selectedLocale;
|
|
673
|
+
const nextLocaleData = {
|
|
674
|
+
...localeData,
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
if (currentLocale) {
|
|
678
|
+
nextLocaleData[currentLocale] = {
|
|
679
|
+
name: currentName,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (!nextLocaleData[nextLocale]) {
|
|
684
|
+
nextLocaleData[nextLocale] = {
|
|
685
|
+
name: '',
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
setLocaleData(nextLocaleData);
|
|
690
|
+
setSelectedLocale(nextLocale);
|
|
691
|
+
form.setValue('name', nextLocaleData[nextLocale].name);
|
|
692
|
+
}}
|
|
648
693
|
>
|
|
649
|
-
<
|
|
650
|
-
<
|
|
651
|
-
|
|
694
|
+
<FormControl>
|
|
695
|
+
<SelectTrigger id="locale-select">
|
|
696
|
+
<SelectValue placeholder={t('selectLanguage')} />
|
|
697
|
+
</SelectTrigger>
|
|
698
|
+
</FormControl>
|
|
652
699
|
<SelectContent>
|
|
653
700
|
{locales.map((locale: Locale) => (
|
|
654
701
|
<SelectItem key={locale.code} value={locale.code}>
|
|
@@ -657,171 +704,167 @@ export default function CategoryPage() {
|
|
|
657
704
|
))}
|
|
658
705
|
</SelectContent>
|
|
659
706
|
</Select>
|
|
660
|
-
</
|
|
661
|
-
|
|
662
|
-
<
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
<
|
|
758
|
-
<
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
707
|
+
</FormItem>
|
|
708
|
+
|
|
709
|
+
<FormField
|
|
710
|
+
control={form.control}
|
|
711
|
+
name="name"
|
|
712
|
+
render={({ field }) => (
|
|
713
|
+
<FormItem>
|
|
714
|
+
<FormLabel>{t('nameRequired')}</FormLabel>
|
|
715
|
+
<FormControl>
|
|
716
|
+
<Input
|
|
717
|
+
{...field}
|
|
718
|
+
placeholder={t('namePlaceholder')}
|
|
719
|
+
onChange={(event) => {
|
|
720
|
+
field.onChange(event.target.value);
|
|
721
|
+
if (selectedLocale) {
|
|
722
|
+
setLocaleData((current) => ({
|
|
723
|
+
...current,
|
|
724
|
+
[selectedLocale]: {
|
|
725
|
+
name: event.target.value,
|
|
726
|
+
},
|
|
727
|
+
}));
|
|
728
|
+
}
|
|
729
|
+
}}
|
|
730
|
+
/>
|
|
731
|
+
</FormControl>
|
|
732
|
+
<FormMessage />
|
|
733
|
+
</FormItem>
|
|
734
|
+
)}
|
|
735
|
+
/>
|
|
736
|
+
|
|
737
|
+
<FormField
|
|
738
|
+
control={form.control}
|
|
739
|
+
name="slug"
|
|
740
|
+
render={({ field }) => (
|
|
741
|
+
<FormItem>
|
|
742
|
+
<FormLabel>{t('slugRequired')}</FormLabel>
|
|
743
|
+
<FormControl>
|
|
744
|
+
<Input {...field} placeholder={t('slugPlaceholder')} />
|
|
745
|
+
</FormControl>
|
|
746
|
+
<FormMessage />
|
|
747
|
+
</FormItem>
|
|
748
|
+
)}
|
|
749
|
+
/>
|
|
750
|
+
|
|
751
|
+
<FormField
|
|
752
|
+
control={form.control}
|
|
753
|
+
name="color"
|
|
754
|
+
render={({ field }) => (
|
|
755
|
+
<FormItem>
|
|
756
|
+
<FormLabel>{t('color')}</FormLabel>
|
|
757
|
+
<FormControl>
|
|
758
|
+
<div className="flex gap-2">
|
|
759
|
+
<Input
|
|
760
|
+
type="color"
|
|
761
|
+
className="h-10 w-20"
|
|
762
|
+
value={field.value || '#000000'}
|
|
763
|
+
onChange={(event) =>
|
|
764
|
+
field.onChange(event.target.value)
|
|
765
|
+
}
|
|
766
|
+
/>
|
|
767
|
+
<Input
|
|
768
|
+
value={field.value || '#000000'}
|
|
769
|
+
onChange={(event) =>
|
|
770
|
+
field.onChange(event.target.value)
|
|
771
|
+
}
|
|
772
|
+
placeholder={t('colorPlaceholder')}
|
|
773
|
+
/>
|
|
774
|
+
</div>
|
|
775
|
+
</FormControl>
|
|
776
|
+
<FormMessage />
|
|
777
|
+
</FormItem>
|
|
778
|
+
)}
|
|
779
|
+
/>
|
|
780
|
+
|
|
781
|
+
<FormField
|
|
782
|
+
control={form.control}
|
|
783
|
+
name="icon"
|
|
784
|
+
render={({ field }) => (
|
|
785
|
+
<FormItem>
|
|
786
|
+
<FormLabel>{t('icon')}</FormLabel>
|
|
787
|
+
<FormControl>
|
|
788
|
+
<div className="flex items-center gap-2">
|
|
789
|
+
<div className="shrink-0">
|
|
790
|
+
{renderIcon(field.value)}
|
|
791
|
+
</div>
|
|
792
|
+
<Input {...field} placeholder={t('iconPlaceholder')} />
|
|
793
|
+
</div>
|
|
794
|
+
</FormControl>
|
|
795
|
+
<FormMessage />
|
|
796
|
+
</FormItem>
|
|
797
|
+
)}
|
|
798
|
+
/>
|
|
799
|
+
|
|
800
|
+
<FormField
|
|
801
|
+
control={form.control}
|
|
802
|
+
name="category_id"
|
|
803
|
+
render={({ field }) => (
|
|
804
|
+
<FormItem>
|
|
805
|
+
<FormLabel>{t('parentCategory')}</FormLabel>
|
|
806
|
+
<Select value={field.value} onValueChange={field.onChange}>
|
|
807
|
+
<FormControl>
|
|
808
|
+
<SelectTrigger className="w-full" id="parent">
|
|
809
|
+
<SelectValue placeholder={t('selectParent')} />
|
|
810
|
+
</SelectTrigger>
|
|
811
|
+
</FormControl>
|
|
812
|
+
<SelectContent>
|
|
813
|
+
<SelectItem value="none">{t('noneRoot')}</SelectItem>
|
|
814
|
+
{Array.isArray(rootCategories) &&
|
|
815
|
+
rootCategories.map((cat: Category) => (
|
|
816
|
+
<SelectItem
|
|
817
|
+
key={cat.id}
|
|
818
|
+
value={String(cat.category_id)}
|
|
819
|
+
>
|
|
820
|
+
{cat.name}
|
|
821
|
+
</SelectItem>
|
|
822
|
+
))}
|
|
823
|
+
</SelectContent>
|
|
824
|
+
</Select>
|
|
825
|
+
<FormMessage />
|
|
826
|
+
</FormItem>
|
|
827
|
+
)}
|
|
828
|
+
/>
|
|
829
|
+
|
|
830
|
+
<FormField
|
|
831
|
+
control={form.control}
|
|
832
|
+
name="status"
|
|
833
|
+
render={({ field }) => (
|
|
834
|
+
<FormItem>
|
|
835
|
+
<FormLabel>{t('status')}</FormLabel>
|
|
836
|
+
<Select value={field.value} onValueChange={field.onChange}>
|
|
837
|
+
<FormControl>
|
|
838
|
+
<SelectTrigger className="w-full" id="status">
|
|
839
|
+
<SelectValue placeholder={t('selectStatus')} />
|
|
840
|
+
</SelectTrigger>
|
|
841
|
+
</FormControl>
|
|
842
|
+
<SelectContent>
|
|
843
|
+
<SelectItem value="active">{t('active')}</SelectItem>
|
|
844
|
+
<SelectItem value="inactive">
|
|
845
|
+
{t('inactive')}
|
|
769
846
|
</SelectItem>
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
status: value,
|
|
783
|
-
})
|
|
784
|
-
}
|
|
847
|
+
</SelectContent>
|
|
848
|
+
</Select>
|
|
849
|
+
<FormMessage />
|
|
850
|
+
</FormItem>
|
|
851
|
+
)}
|
|
852
|
+
/>
|
|
853
|
+
|
|
854
|
+
<SheetFooter className="px-0">
|
|
855
|
+
<Button
|
|
856
|
+
type="submit"
|
|
857
|
+
disabled={!form.formState.isValid}
|
|
858
|
+
className="w-full transition-colors hover:bg-primary/90"
|
|
785
859
|
>
|
|
786
|
-
<
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
</div>
|
|
796
|
-
)}
|
|
797
|
-
|
|
798
|
-
<DialogFooter className="mt-4">
|
|
799
|
-
<Button
|
|
800
|
-
variant="outline"
|
|
801
|
-
onClick={() => {
|
|
802
|
-
setIsEditDialogOpen(false);
|
|
803
|
-
setSelectedCategory(null);
|
|
804
|
-
setIsNewCategory(false);
|
|
805
|
-
}}
|
|
806
|
-
>
|
|
807
|
-
<X className="mr-2 h-4 w-4" />
|
|
808
|
-
{t('cancel')}
|
|
809
|
-
</Button>
|
|
810
|
-
<Button
|
|
811
|
-
onClick={handleSaveCategory}
|
|
812
|
-
disabled={
|
|
813
|
-
!selectedCategory?.slug ||
|
|
814
|
-
!selectedCategory?.locale ||
|
|
815
|
-
!Object.values(selectedCategory.locale).some((l: any) => l.name)
|
|
816
|
-
}
|
|
817
|
-
className="transition-colors hover:bg-primary/90"
|
|
818
|
-
>
|
|
819
|
-
<Save className="mr-2 h-4 w-4" />
|
|
820
|
-
{isNewCategory ? t('createCategoryButton') : t('saveChanges')}
|
|
821
|
-
</Button>
|
|
822
|
-
</DialogFooter>
|
|
823
|
-
</DialogContent>
|
|
824
|
-
</Dialog>
|
|
825
|
-
</div>
|
|
860
|
+
<Save className="mr-2 h-4 w-4" />
|
|
861
|
+
{isNewCategory ? t('createCategoryButton') : t('saveChanges')}
|
|
862
|
+
</Button>
|
|
863
|
+
</SheetFooter>
|
|
864
|
+
</form>
|
|
865
|
+
</Form>
|
|
866
|
+
</SheetContent>
|
|
867
|
+
</Sheet>
|
|
868
|
+
</Page>
|
|
826
869
|
);
|
|
827
870
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hed-hog/category",
|
|
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": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"@hed-hog/api-prisma": "0.0.5",
|
|
13
13
|
"@hed-hog/api": "0.0.4",
|
|
14
14
|
"@hed-hog/api-locale": "0.0.13",
|
|
15
|
-
"@hed-hog/core": "0.0.
|
|
15
|
+
"@hed-hog/core": "0.0.285",
|
|
16
16
|
"@hed-hog/api-pagination": "0.0.6"
|
|
17
17
|
},
|
|
18
18
|
"exports": {
|