@hed-hog/category 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 +2 -0
- package/hedhog/frontend/app/page.tsx.ejs +507 -457
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
```markdown
|
|
1
2
|
# @hed-hog/category
|
|
2
3
|
|
|
3
4
|
## 1. Visão geral do módulo
|
|
@@ -322,3 +323,4 @@ Authorization: Bearer <token>
|
|
|
322
323
|
---
|
|
323
324
|
|
|
324
325
|
Este módulo é essencial para a organização e categorização dos conteúdos e produtos no sistema HedHog, garantindo flexibilidade e suporte multilíngue com controle de acesso robusto.
|
|
326
|
+
```
|
|
@@ -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,9 +170,9 @@ export default function CategoryPage() {
|
|
|
123
170
|
}
|
|
124
171
|
}, [currentLocaleCode]);
|
|
125
172
|
|
|
126
|
-
const { data: categoriesResult, refetch: refetchCategories } = useQuery<
|
|
127
|
-
PaginationResult<Category>
|
|
128
|
-
>({
|
|
173
|
+
const { data: categoriesResult, refetch: refetchCategories } = useQuery<
|
|
174
|
+
PaginationResult<Category>
|
|
175
|
+
>({
|
|
129
176
|
queryKey: [
|
|
130
177
|
'categories',
|
|
131
178
|
debouncedSearch,
|
|
@@ -147,10 +194,10 @@ export default function CategoryPage() {
|
|
|
147
194
|
});
|
|
148
195
|
return response.data as PaginationResult<Category>;
|
|
149
196
|
},
|
|
150
|
-
});
|
|
151
|
-
const { data = [], total = 0 } = categoriesResult ?? {};
|
|
197
|
+
});
|
|
198
|
+
const { data: categories = [], total = 0 } = categoriesResult ?? {};
|
|
152
199
|
|
|
153
|
-
const { data: rootCategories = [] } = useQuery<Category[]>({
|
|
200
|
+
const { data: rootCategories = [] } = useQuery<Category[]>({
|
|
154
201
|
queryKey: ['root-categories'],
|
|
155
202
|
queryFn: async () => {
|
|
156
203
|
const response = await request({
|
|
@@ -158,9 +205,9 @@ export default function CategoryPage() {
|
|
|
158
205
|
});
|
|
159
206
|
return (response.data || []) as Category[];
|
|
160
207
|
},
|
|
161
|
-
enabled: true,
|
|
162
|
-
staleTime: 0,
|
|
163
|
-
refetchOnMount: true,
|
|
208
|
+
enabled: true,
|
|
209
|
+
staleTime: 0,
|
|
210
|
+
refetchOnMount: true,
|
|
164
211
|
});
|
|
165
212
|
|
|
166
213
|
const { data: statsData, refetch: refetchStats } = useQuery<any>({
|
|
@@ -173,12 +220,6 @@ export default function CategoryPage() {
|
|
|
173
220
|
},
|
|
174
221
|
});
|
|
175
222
|
|
|
176
|
-
useEffect(() => {
|
|
177
|
-
if (data) {
|
|
178
|
-
setCategories(data);
|
|
179
|
-
}
|
|
180
|
-
}, [data]);
|
|
181
|
-
|
|
182
223
|
const renderIcon = (iconName?: string) => {
|
|
183
224
|
const toPascalCase = (str: string) =>
|
|
184
225
|
str.replace(/(^\w|-\w)/g, (match) =>
|
|
@@ -202,22 +243,26 @@ export default function CategoryPage() {
|
|
|
202
243
|
};
|
|
203
244
|
|
|
204
245
|
const handleNewCategory = (): void => {
|
|
205
|
-
const
|
|
206
|
-
slug: '',
|
|
207
|
-
category_id: null,
|
|
208
|
-
color: '#000000',
|
|
209
|
-
icon: '',
|
|
210
|
-
status: 'active',
|
|
211
|
-
locale: {},
|
|
212
|
-
};
|
|
213
|
-
|
|
246
|
+
const nextLocaleData: Record<string, { name: string }> = {};
|
|
214
247
|
locales.forEach((locale: Locale) => {
|
|
215
|
-
|
|
248
|
+
nextLocaleData[locale.code] = {
|
|
216
249
|
name: '',
|
|
217
250
|
};
|
|
218
251
|
});
|
|
219
252
|
|
|
220
|
-
|
|
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
|
+
});
|
|
221
266
|
setIsNewCategory(true);
|
|
222
267
|
setIsEditDialogOpen(true);
|
|
223
268
|
};
|
|
@@ -261,9 +306,21 @@ export default function CategoryPage() {
|
|
|
261
306
|
}
|
|
262
307
|
});
|
|
263
308
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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',
|
|
267
324
|
});
|
|
268
325
|
setIsNewCategory(false);
|
|
269
326
|
setIsEditDialogOpen(true);
|
|
@@ -273,22 +330,28 @@ export default function CategoryPage() {
|
|
|
273
330
|
}
|
|
274
331
|
};
|
|
275
332
|
|
|
276
|
-
const handleSaveCategory = async () => {
|
|
277
|
-
|
|
333
|
+
const handleSaveCategory = form.handleSubmit(async (values) => {
|
|
334
|
+
const mergedLocaleData = {
|
|
335
|
+
...localeData,
|
|
336
|
+
[selectedLocale]: {
|
|
337
|
+
name: values.name,
|
|
338
|
+
},
|
|
339
|
+
};
|
|
278
340
|
|
|
279
341
|
const payload = {
|
|
280
|
-
locale:
|
|
281
|
-
slug:
|
|
282
|
-
category_id:
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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,
|
|
286
349
|
};
|
|
287
350
|
|
|
288
351
|
try {
|
|
289
|
-
if (
|
|
352
|
+
if (editingCategoryId) {
|
|
290
353
|
await request({
|
|
291
|
-
url: `/category/${
|
|
354
|
+
url: `/category/${editingCategoryId}`,
|
|
292
355
|
method: 'PATCH',
|
|
293
356
|
data: payload,
|
|
294
357
|
});
|
|
@@ -303,13 +366,16 @@ export default function CategoryPage() {
|
|
|
303
366
|
}
|
|
304
367
|
|
|
305
368
|
setIsEditDialogOpen(false);
|
|
369
|
+
setEditingCategoryId(null);
|
|
370
|
+
setLocaleData({});
|
|
371
|
+
form.reset();
|
|
306
372
|
await refetchCategories();
|
|
307
373
|
await refetchStats();
|
|
308
374
|
} catch (error) {
|
|
309
375
|
console.error(error);
|
|
310
376
|
toast.error(t('errorSave'));
|
|
311
377
|
}
|
|
312
|
-
};
|
|
378
|
+
});
|
|
313
379
|
|
|
314
380
|
const handleDeleteCategory = async (categoryId: number): Promise<void> => {
|
|
315
381
|
try {
|
|
@@ -328,19 +394,16 @@ export default function CategoryPage() {
|
|
|
328
394
|
|
|
329
395
|
const handleSearchChange = (value: string): void => {
|
|
330
396
|
setSearchTerm(value);
|
|
397
|
+
setPage(1);
|
|
331
398
|
};
|
|
332
399
|
|
|
333
400
|
useEffect(() => {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
pageSize,
|
|
341
|
-
statusFilter,
|
|
342
|
-
parentFilter,
|
|
343
|
-
]);
|
|
401
|
+
if (!isEditDialogOpen || !selectedLocale) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
form.setValue('name', localeData[selectedLocale]?.name || '');
|
|
406
|
+
}, [form, isEditDialogOpen, localeData, selectedLocale]);
|
|
344
407
|
|
|
345
408
|
const getStatusBadge = (status: string) => {
|
|
346
409
|
return status === 'active' ? (
|
|
@@ -352,13 +415,41 @@ export default function CategoryPage() {
|
|
|
352
415
|
);
|
|
353
416
|
};
|
|
354
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
|
+
|
|
355
449
|
return (
|
|
356
|
-
<
|
|
450
|
+
<Page>
|
|
357
451
|
<PageHeader
|
|
358
|
-
breadcrumbs={[
|
|
359
|
-
{ label: 'Home', href: '/' },
|
|
360
|
-
{ label: t('description') },
|
|
361
|
-
]}
|
|
452
|
+
breadcrumbs={[{ label: 'Home', href: '/' }, { label: t('title') }]}
|
|
362
453
|
actions={[
|
|
363
454
|
{
|
|
364
455
|
label: t('newCategory'),
|
|
@@ -370,235 +461,163 @@ export default function CategoryPage() {
|
|
|
370
461
|
description={t('description')}
|
|
371
462
|
/>
|
|
372
463
|
|
|
373
|
-
<
|
|
374
|
-
<Card className="transition-shadow hover:shadow-md p-0">
|
|
375
|
-
<CardContent className="p-4">
|
|
376
|
-
<div className="flex items-center space-x-3">
|
|
377
|
-
<div className="rounded-full bg-blue-100 p-2 dark:bg-blue-900">
|
|
378
|
-
<Layers className="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
|
379
|
-
</div>
|
|
380
|
-
<div>
|
|
381
|
-
<p className="text-sm font-medium text-muted-foreground">
|
|
382
|
-
{t('totalCategories')}
|
|
383
|
-
</p>
|
|
384
|
-
<p className="text-2xl font-bold">{statsData?.total || 0}</p>
|
|
385
|
-
</div>
|
|
386
|
-
</div>
|
|
387
|
-
</CardContent>
|
|
388
|
-
</Card>
|
|
389
|
-
|
|
390
|
-
<Card className="transition-shadow hover:shadow-md p-0">
|
|
391
|
-
<CardContent className="p-4">
|
|
392
|
-
<div className="flex items-center space-x-3">
|
|
393
|
-
<div className="rounded-full bg-green-100 p-2 dark:bg-green-900">
|
|
394
|
-
<Tag className="h-6 w-6 text-green-600 dark:text-green-400" />
|
|
395
|
-
</div>
|
|
396
|
-
<div>
|
|
397
|
-
<p className="text-sm font-medium text-muted-foreground">
|
|
398
|
-
{t('actives')}
|
|
399
|
-
</p>
|
|
400
|
-
<p className="text-2xl font-bold">
|
|
401
|
-
{statsData?.totalActive || 0}
|
|
402
|
-
</p>
|
|
403
|
-
</div>
|
|
404
|
-
</div>
|
|
405
|
-
</CardContent>
|
|
406
|
-
</Card>
|
|
407
|
-
|
|
408
|
-
<Card className="transition-shadow hover:shadow-md p-0">
|
|
409
|
-
<CardContent className="p-4">
|
|
410
|
-
<div className="flex items-center space-x-3">
|
|
411
|
-
<div className="rounded-full bg-orange-100 p-2 dark:bg-orange-900">
|
|
412
|
-
<Tag className="h-6 w-6 text-orange-600 dark:text-orange-400" />
|
|
413
|
-
</div>
|
|
414
|
-
<div>
|
|
415
|
-
<p className="text-sm font-medium text-muted-foreground">
|
|
416
|
-
{t('inactives')}
|
|
417
|
-
</p>
|
|
418
|
-
<p className="text-2xl font-bold">
|
|
419
|
-
{statsData?.totalInactive || 0}
|
|
420
|
-
</p>
|
|
421
|
-
</div>
|
|
422
|
-
</div>
|
|
423
|
-
</CardContent>
|
|
424
|
-
</Card>
|
|
425
|
-
|
|
426
|
-
<Card className="transition-shadow hover:shadow-md p-0">
|
|
427
|
-
<CardContent className="p-4">
|
|
428
|
-
<div className="flex items-center space-x-3">
|
|
429
|
-
<div className="rounded-full bg-purple-100 p-2 dark:bg-purple-900">
|
|
430
|
-
<FolderTree className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
|
431
|
-
</div>
|
|
432
|
-
<div>
|
|
433
|
-
<p className="text-sm font-medium text-muted-foreground">
|
|
434
|
-
{t('root')}
|
|
435
|
-
</p>
|
|
436
|
-
<p className="text-2xl font-bold">
|
|
437
|
-
{statsData?.totalRoot || 0}
|
|
438
|
-
</p>
|
|
439
|
-
</div>
|
|
440
|
-
</div>
|
|
441
|
-
</CardContent>
|
|
442
|
-
</Card>
|
|
443
|
-
</div>
|
|
444
|
-
|
|
445
|
-
<div className="flex flex-col gap-4 sm:flex-row my-4">
|
|
446
|
-
<SearchBar
|
|
447
|
-
searchQuery={searchTerm}
|
|
448
|
-
onSearchChange={handleSearchChange}
|
|
449
|
-
onSearch={() => refetchCategories()}
|
|
450
|
-
placeholder={t('searchPlaceholder')}
|
|
451
|
-
/>
|
|
464
|
+
<StatsCards stats={statsCards} />
|
|
452
465
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
+
/>
|
|
473
497
|
|
|
474
498
|
<div className="space-y-4">
|
|
475
499
|
{categories.length > 0 ? (
|
|
476
|
-
<div className="
|
|
500
|
+
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
|
477
501
|
{categories.map((category) => (
|
|
478
502
|
<Card
|
|
479
503
|
key={category.id}
|
|
480
504
|
onDoubleClick={() => handleEditCategory(category)}
|
|
481
505
|
className="cursor-pointer transition-all duration-200 hover:border-primary/20 hover:shadow-md"
|
|
482
506
|
>
|
|
483
|
-
<CardContent className="p-
|
|
484
|
-
<div className="flex items-start justify-between gap-
|
|
485
|
-
<div className="flex-
|
|
486
|
-
<div
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
>
|
|
495
|
-
|
|
496
|
-
{renderIcon(category.icon)}
|
|
497
|
-
</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)}
|
|
498
520
|
</div>
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
/>
|
|
519
|
-
{category.color}
|
|
520
|
-
</span>
|
|
521
|
-
)}
|
|
522
|
-
{category.icon && (
|
|
523
|
-
<span>
|
|
524
|
-
<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')}:
|
|
525
540
|
</span>
|
|
526
|
-
|
|
527
|
-
|
|
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
|
+
)}
|
|
528
558
|
</div>
|
|
529
559
|
</div>
|
|
530
560
|
</div>
|
|
561
|
+
</div>
|
|
531
562
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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"
|
|
549
601
|
>
|
|
550
|
-
<Trash2 className="mr-1 h-4 w-4" />
|
|
551
602
|
{t('delete')}
|
|
552
|
-
</
|
|
553
|
-
</
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
<AlertDialogTitle>
|
|
557
|
-
{t('confirmDelete')}
|
|
558
|
-
</AlertDialogTitle>
|
|
559
|
-
<AlertDialogDescription>
|
|
560
|
-
{t('deleteDescription')}
|
|
561
|
-
</AlertDialogDescription>
|
|
562
|
-
</AlertDialogHeader>
|
|
563
|
-
<AlertDialogFooter>
|
|
564
|
-
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
|
|
565
|
-
<AlertDialogAction
|
|
566
|
-
onClick={() =>
|
|
567
|
-
handleDeleteCategory(
|
|
568
|
-
Number(category.category_id)
|
|
569
|
-
)
|
|
570
|
-
}
|
|
571
|
-
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
572
|
-
>
|
|
573
|
-
{t('delete')}
|
|
574
|
-
</AlertDialogAction>
|
|
575
|
-
</AlertDialogFooter>
|
|
576
|
-
</AlertDialogContent>
|
|
577
|
-
</AlertDialog>
|
|
578
|
-
</div>
|
|
603
|
+
</AlertDialogAction>
|
|
604
|
+
</AlertDialogFooter>
|
|
605
|
+
</AlertDialogContent>
|
|
606
|
+
</AlertDialog>
|
|
579
607
|
</div>
|
|
580
608
|
</CardContent>
|
|
581
609
|
</Card>
|
|
582
610
|
))}
|
|
583
611
|
</div>
|
|
584
612
|
) : (
|
|
585
|
-
<
|
|
586
|
-
<
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
<p className="text-muted-foreground">{t('adjustFilters')}</p>
|
|
594
|
-
</div>
|
|
595
|
-
<Button onClick={handleNewCategory}>
|
|
596
|
-
<Plus className="mr-2 h-4 w-4" />
|
|
597
|
-
{t('createFirstCategory')}
|
|
598
|
-
</Button>
|
|
599
|
-
</div>
|
|
600
|
-
</CardContent>
|
|
601
|
-
</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
|
+
/>
|
|
602
621
|
)}
|
|
603
622
|
|
|
604
623
|
<PaginationFooter
|
|
@@ -606,42 +625,77 @@ export default function CategoryPage() {
|
|
|
606
625
|
pageSize={pageSize}
|
|
607
626
|
totalItems={total}
|
|
608
627
|
onPageChange={setPage}
|
|
609
|
-
onPageSizeChange={
|
|
628
|
+
onPageSizeChange={(nextPageSize) => {
|
|
629
|
+
setPageSize(nextPageSize);
|
|
630
|
+
setPage(1);
|
|
631
|
+
}}
|
|
610
632
|
pageSizeOptions={[10, 20, 30, 40, 50]}
|
|
611
633
|
/>
|
|
612
634
|
</div>
|
|
613
635
|
|
|
614
|
-
<
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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">
|
|
618
651
|
<Edit className="h-5 w-5" />
|
|
619
652
|
<span>
|
|
620
653
|
{isNewCategory ? t('newCategoryTitle') : t('editCategory')}
|
|
621
654
|
</span>
|
|
622
|
-
</
|
|
623
|
-
<
|
|
655
|
+
</SheetTitle>
|
|
656
|
+
<SheetDescription>
|
|
624
657
|
{isNewCategory ? t('createDescription') : t('editDescription')}
|
|
625
|
-
</
|
|
626
|
-
</
|
|
627
|
-
|
|
628
|
-
{
|
|
629
|
-
<
|
|
630
|
-
<
|
|
631
|
-
<
|
|
632
|
-
htmlFor="locale-select"
|
|
633
|
-
className="flex items-center gap-2"
|
|
634
|
-
>
|
|
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">
|
|
635
665
|
<Globe className="h-4 w-4" />
|
|
636
666
|
{t('language')}
|
|
637
|
-
</
|
|
667
|
+
</FormLabel>
|
|
638
668
|
<Select
|
|
639
669
|
value={selectedLocale}
|
|
640
|
-
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
|
+
}}
|
|
641
693
|
>
|
|
642
|
-
<
|
|
643
|
-
<
|
|
644
|
-
|
|
694
|
+
<FormControl>
|
|
695
|
+
<SelectTrigger id="locale-select">
|
|
696
|
+
<SelectValue placeholder={t('selectLanguage')} />
|
|
697
|
+
</SelectTrigger>
|
|
698
|
+
</FormControl>
|
|
645
699
|
<SelectContent>
|
|
646
700
|
{locales.map((locale: Locale) => (
|
|
647
701
|
<SelectItem key={locale.code} value={locale.code}>
|
|
@@ -650,171 +704,167 @@ export default function CategoryPage() {
|
|
|
650
704
|
))}
|
|
651
705
|
</SelectContent>
|
|
652
706
|
</Select>
|
|
653
|
-
</
|
|
654
|
-
|
|
655
|
-
<
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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
|
-
|
|
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')}
|
|
762
846
|
</SelectItem>
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
status: value,
|
|
776
|
-
})
|
|
777
|
-
}
|
|
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"
|
|
778
859
|
>
|
|
779
|
-
<
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
</div>
|
|
789
|
-
)}
|
|
790
|
-
|
|
791
|
-
<DialogFooter className="mt-4">
|
|
792
|
-
<Button
|
|
793
|
-
variant="outline"
|
|
794
|
-
onClick={() => {
|
|
795
|
-
setIsEditDialogOpen(false);
|
|
796
|
-
setSelectedCategory(null);
|
|
797
|
-
setIsNewCategory(false);
|
|
798
|
-
}}
|
|
799
|
-
>
|
|
800
|
-
<X className="mr-2 h-4 w-4" />
|
|
801
|
-
{t('cancel')}
|
|
802
|
-
</Button>
|
|
803
|
-
<Button
|
|
804
|
-
onClick={handleSaveCategory}
|
|
805
|
-
disabled={
|
|
806
|
-
!selectedCategory?.slug ||
|
|
807
|
-
!selectedCategory?.locale ||
|
|
808
|
-
!Object.values(selectedCategory.locale).some((l: any) => l.name)
|
|
809
|
-
}
|
|
810
|
-
className="transition-colors hover:bg-primary/90"
|
|
811
|
-
>
|
|
812
|
-
<Save className="mr-2 h-4 w-4" />
|
|
813
|
-
{isNewCategory ? t('createCategoryButton') : t('saveChanges')}
|
|
814
|
-
</Button>
|
|
815
|
-
</DialogFooter>
|
|
816
|
-
</DialogContent>
|
|
817
|
-
</Dialog>
|
|
818
|
-
</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>
|
|
819
869
|
);
|
|
820
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.286",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
"@nestjs/jwt": "^11",
|
|
11
11
|
"@nestjs/mapped-types": "*",
|
|
12
12
|
"@hed-hog/api-prisma": "0.0.5",
|
|
13
|
-
"@hed-hog/core": "0.0.279",
|
|
14
13
|
"@hed-hog/api-pagination": "0.0.6",
|
|
14
|
+
"@hed-hog/api-locale": "0.0.13",
|
|
15
15
|
"@hed-hog/api": "0.0.4",
|
|
16
|
-
"@hed-hog/
|
|
16
|
+
"@hed-hog/core": "0.0.286"
|
|
17
17
|
},
|
|
18
18
|
"exports": {
|
|
19
19
|
".": {
|