@hed-hog/content 0.0.279 → 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.
@@ -192,9 +192,9 @@ let ContentService = class ContentService {
192
192
  if (!localeRecord) {
193
193
  throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('localeNotFound', localeStr, `Locale ${localeCode} not found`).replace('{{locale}}', localeCode));
194
194
  }
195
- const existingLocale = await this.prisma.category_locale.findFirst({
195
+ const existingLocale = await this.prisma.content_locale.findFirst({
196
196
  where: {
197
- category_id: Number(id),
197
+ content_id: Number(id),
198
198
  locale_id: localeRecord.id,
199
199
  },
200
200
  });
@@ -1 +1 @@
1
- {"version":3,"file":"content.service.js","sourceRoot":"","sources":["../src/content.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"content.service.js","sourceRoot":"","sources":["../src/content.service.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,15 @@
1
1
  'use client';
2
2
 
3
- import { PageHeader } from '@/components/entity-list';
3
+ import {
4
+ EmptyState,
5
+ Page,
6
+ PageHeader,
7
+ PaginationFooter,
8
+ SearchBar,
9
+ StatsCards,
10
+ type SearchBarControl,
11
+ type StatCardConfig,
12
+ } from '@/components/entity-list';
4
13
  import { RichTextEditor } from '@/components/rich-text-editor';
5
14
  import {
6
15
  AlertDialog,
@@ -11,19 +20,9 @@ import {
11
20
  AlertDialogFooter,
12
21
  AlertDialogHeader,
13
22
  AlertDialogTitle,
14
- AlertDialogTrigger,
15
23
  } from '@/components/ui/alert-dialog';
16
24
  import { Badge } from '@/components/ui/badge';
17
25
  import { Button } from '@/components/ui/button';
18
- import { Card, CardContent } from '@/components/ui/card';
19
- import {
20
- Dialog,
21
- DialogContent,
22
- DialogDescription,
23
- DialogFooter,
24
- DialogHeader,
25
- DialogTitle,
26
- } from '@/components/ui/dialog';
27
26
  import {
28
27
  Form,
29
28
  FormControl,
@@ -41,6 +40,13 @@ import {
41
40
  SelectTrigger,
42
41
  SelectValue,
43
42
  } from '@/components/ui/select';
43
+ import {
44
+ Sheet,
45
+ SheetContent,
46
+ SheetDescription,
47
+ SheetHeader,
48
+ SheetTitle,
49
+ } from '@/components/ui/sheet';
44
50
  import { useDebounce } from '@/hooks/use-debounce';
45
51
  import { formatDateTime } from '@/lib/format-date';
46
52
  import { cn } from '@/lib/utils';
@@ -49,13 +55,11 @@ import { zodResolver } from '@hookform/resolvers/zod';
49
55
  import {
50
56
  Calendar,
51
57
  Edit,
52
- Eye,
53
58
  FileText,
54
59
  Globe,
55
60
  Languages,
56
61
  Loader2,
57
62
  Plus,
58
- Search,
59
63
  Trash2,
60
64
  } from 'lucide-react';
61
65
  import { useTranslations } from 'next-intl';
@@ -115,10 +119,17 @@ type Locale = {
115
119
  export default function ContentsPage() {
116
120
  const [searchTerm, setSearchTerm] = useState('');
117
121
  const debouncedSearch = useDebounce(searchTerm);
118
- const [isFormDialogOpen, setIsFormDialogOpen] = useState(false);
122
+ const [page, setPage] = useState(1);
123
+ const [pageSize, setPageSize] = useState(12);
124
+ const [statusFilter, setStatusFilter] = useState('all');
125
+ const [isFormSheetOpen, setIsFormSheetOpen] = useState(false);
119
126
  const [editingContentId, setEditingContentId] = useState<number | null>(null);
127
+ const [deleteTarget, setDeleteTarget] = useState<Content | null>(null);
120
128
  const [isSubmitting, setIsSubmitting] = useState(false);
121
129
  const [selectedLocale, setSelectedLocale] = useState<string>('');
130
+ const [localesData, setLocalesData] = useState<
131
+ Record<string, { title: string; body: string }>
132
+ >({});
122
133
  const { request, currentLocaleCode, getSettingValue } = useApp();
123
134
  const t = useTranslations('content.ContentPage');
124
135
 
@@ -167,51 +178,159 @@ export default function ContentsPage() {
167
178
  },
168
179
  });
169
180
 
170
- const { data: contents, refetch: refetch } = useQuery<
171
- PaginationResult<Content>
172
- >({
173
- queryKey: ['content', currentLocaleCode],
181
+ const { data: contents, refetch: refetch } = useQuery<
182
+ PaginationResult<Content>
183
+ >({
184
+ queryKey: [
185
+ 'content',
186
+ currentLocaleCode,
187
+ page,
188
+ pageSize,
189
+ debouncedSearch,
190
+ statusFilter,
191
+ ],
174
192
  queryFn: async () => {
175
193
  const response = await request({
176
194
  url: '/content',
177
195
  params: {
196
+ page,
197
+ pageSize,
178
198
  search: debouncedSearch,
199
+ status: statusFilter,
179
200
  },
180
201
  });
181
202
  return response.data as PaginationResult<Content>;
182
203
  },
183
- });
184
- const contentList = contents ?? {
185
- data: [],
186
- total: 0,
187
- page: 1,
188
- pageSize: 10,
189
- lastPage: 1,
190
- prev: null,
191
- next: null,
192
- };
204
+ });
205
+ const contentList = contents ?? {
206
+ data: [],
207
+ total: 0,
208
+ page,
209
+ pageSize,
210
+ lastPage: 1,
211
+ prev: null,
212
+ next: null,
213
+ };
214
+
215
+ const totalPages = Math.max(1, contentList.lastPage || 1);
216
+ const currentPage = Math.min(
217
+ Math.max(contentList.page || page, 1),
218
+ totalPages
219
+ );
193
220
 
194
221
  const getStatusBadge = (status: string) => {
195
222
  const statusOption = statusOptions.find((s) => s.value === status);
196
223
  return statusOption || statusOptions[0];
197
224
  };
198
225
 
226
+ const statsCards: StatCardConfig[] = [
227
+ {
228
+ title: t('statsTotalPages'),
229
+ value: stats?.total ?? 0,
230
+ icon: <FileText className="size-5" />,
231
+ iconBgColor: 'bg-blue-50',
232
+ iconColor: 'text-blue-600',
233
+ },
234
+ {
235
+ title: t('statsPublished'),
236
+ value: stats?.totalPublished ?? 0,
237
+ icon: <Edit className="size-5" />,
238
+ iconBgColor: 'bg-green-50',
239
+ iconColor: 'text-green-600',
240
+ },
241
+ {
242
+ title: t('statsDrafts'),
243
+ value: stats?.totalDraft ?? 0,
244
+ icon: <Calendar className="size-5" />,
245
+ iconBgColor: 'bg-amber-50',
246
+ iconColor: 'text-amber-600',
247
+ },
248
+ {
249
+ title: t('statsActiveLanguages'),
250
+ value: stats?.totalEnabledLanguages ?? 0,
251
+ icon: <Languages className="size-5" />,
252
+ iconBgColor: 'bg-sky-50',
253
+ iconColor: 'text-sky-600',
254
+ },
255
+ ];
256
+
257
+ const searchControls: SearchBarControl[] = [
258
+ {
259
+ id: 'status',
260
+ type: 'select',
261
+ value: statusFilter,
262
+ onChange: (value) => {
263
+ setStatusFilter(value);
264
+ setPage(1);
265
+ },
266
+ placeholder: t('formPlaceholderStatus'),
267
+ options: [
268
+ { value: 'all', label: t('filterAllStatus') },
269
+ { value: 'draft', label: t('statusDraft') },
270
+ { value: 'published', label: t('statusPublished') },
271
+ ],
272
+ },
273
+ ];
274
+
275
+ const getContentIdentifier = (content: Content) =>
276
+ content.content_id || content.id;
277
+
278
+ const persistSelectedLocale = () => {
279
+ const currentValues = form.getValues();
280
+
281
+ setLocalesData((current) => ({
282
+ ...current,
283
+ [selectedLocale || currentLocaleCode]: {
284
+ title: currentValues.title,
285
+ body: currentValues.body,
286
+ },
287
+ }));
288
+ };
289
+
290
+ const handleLocaleChange = (newLocale: string) => {
291
+ const currentValues = form.getValues();
292
+ const nextLocalesData = {
293
+ ...localesData,
294
+ [selectedLocale || currentLocaleCode]: {
295
+ title: currentValues.title,
296
+ body: currentValues.body,
297
+ },
298
+ };
299
+ const nextLocaleData = nextLocalesData[newLocale] || {
300
+ title: '',
301
+ body: '',
302
+ };
303
+
304
+ setLocalesData(nextLocalesData);
305
+ setSelectedLocale(newLocale);
306
+ form.setValue('title', nextLocaleData.title);
307
+ form.setValue('body', nextLocaleData.body);
308
+ };
309
+
199
310
  const handleNewContent = (): void => {
311
+ const initialLocale = currentLocaleCode;
312
+
200
313
  form.reset({
201
314
  slug: '',
202
315
  status: 'draft',
203
316
  title: '',
204
317
  body: '',
205
318
  });
206
- setSelectedLocale(currentLocaleCode);
319
+ setLocalesData({
320
+ [initialLocale]: {
321
+ title: '',
322
+ body: '',
323
+ },
324
+ });
325
+ setSelectedLocale(initialLocale);
207
326
  setEditingContentId(null);
208
- setIsFormDialogOpen(true);
327
+ setIsFormSheetOpen(true);
209
328
  };
210
329
 
211
330
  const handleEditContent = async (content: Content): Promise<void> => {
212
331
  try {
213
332
  const response = await request<any>({
214
- url: `/content/${content.content_id || content.id}`,
333
+ url: `/content/${getContentIdentifier(content)}`,
215
334
  method: 'GET',
216
335
  });
217
336
 
@@ -238,7 +357,7 @@ export default function ContentsPage() {
238
357
  }
239
358
 
240
359
  setSelectedLocale(initialLocale);
241
- (form as any).localesData = allLocalesData;
360
+ setLocalesData(allLocalesData);
242
361
  form.reset({
243
362
  slug: fullContent.slug || '',
244
363
  status: fullContent.status || 'draft',
@@ -246,8 +365,8 @@ export default function ContentsPage() {
246
365
  body: allLocalesData[initialLocale]?.body || '',
247
366
  });
248
367
 
249
- setEditingContentId(content.content_id || content.id!);
250
- setIsFormDialogOpen(true);
368
+ setEditingContentId(getContentIdentifier(content)!);
369
+ setIsFormSheetOpen(true);
251
370
  } catch (error) {
252
371
  console.error('Error loading content:', error);
253
372
  toast.error(t('errorLoad'));
@@ -257,15 +376,24 @@ export default function ContentsPage() {
257
376
  const onSubmit = async (values: z.infer<typeof contentSchema>) => {
258
377
  setIsSubmitting(true);
259
378
  try {
379
+ const mergedLocales = {
380
+ ...localesData,
381
+ [selectedLocale || currentLocaleCode]: {
382
+ title: values.title,
383
+ body: values.body,
384
+ },
385
+ };
386
+ const payloadLocales = Object.fromEntries(
387
+ Object.entries(mergedLocales).filter(
388
+ ([, localeValue]) =>
389
+ localeValue.title.trim().length > 0 ||
390
+ localeValue.body.trim().length > 0
391
+ )
392
+ );
260
393
  const payload = {
261
394
  slug: values.slug,
262
395
  status: values.status,
263
- locale: {
264
- [selectedLocale]: {
265
- title: values.title,
266
- body: values.body,
267
- },
268
- },
396
+ locale: payloadLocales,
269
397
  };
270
398
 
271
399
  if (editingContentId) {
@@ -284,7 +412,8 @@ export default function ContentsPage() {
284
412
  toast.success(t('successCreate'));
285
413
  }
286
414
 
287
- setIsFormDialogOpen(false);
415
+ setIsFormSheetOpen(false);
416
+ setLocalesData({});
288
417
  refetch();
289
418
  refetchStats();
290
419
  } catch (error: any) {
@@ -296,32 +425,41 @@ export default function ContentsPage() {
296
425
  }
297
426
  };
298
427
 
299
- const handleDeleteContent = (contentId: number): void => {
300
- request({
301
- url: `/content/${contentId}`,
302
- method: 'DELETE',
303
- }).then(() => {
428
+ const handleDeleteContent = async (): Promise<void> => {
429
+ if (!deleteTarget) {
430
+ return;
431
+ }
432
+
433
+ try {
434
+ await request({
435
+ url: `/content/${getContentIdentifier(deleteTarget)}`,
436
+ method: 'DELETE',
437
+ });
304
438
  toast.success(t('successDelete'));
439
+ setDeleteTarget(null);
305
440
  refetch();
306
441
  refetchStats();
307
- });
442
+ } catch (error) {
443
+ console.error('Error deleting content:', error);
444
+ toast.error(t('errorUpdate'));
445
+ }
308
446
  };
309
447
 
310
448
  const handleSearchChange = (value: string): void => {
311
449
  setSearchTerm(value);
450
+ setPage(1);
312
451
  };
313
452
 
314
453
  useEffect(() => {
315
- refetch();
316
- }, [debouncedSearch]);
454
+ if (!selectedLocale) {
455
+ setSelectedLocale(currentLocaleCode);
456
+ }
457
+ }, [currentLocaleCode, selectedLocale]);
317
458
 
318
459
  return (
319
- <div className="flex flex-col h-screen px-4">
460
+ <Page>
320
461
  <PageHeader
321
- breadcrumbs={[
322
- { label: 'Home', href: '/' },
323
- { label: t('description') },
324
- ]}
462
+ breadcrumbs={[{ label: 'Home', href: '/' }, { label: t('title') }]}
325
463
  title={t('title')}
326
464
  description={t('description')}
327
465
  actions={[
@@ -329,251 +467,176 @@ export default function ContentsPage() {
329
467
  label: t('buttonNew'),
330
468
  onClick: () => handleNewContent(),
331
469
  variant: 'default',
470
+ icon: <Plus className="size-4" />,
332
471
  },
333
472
  ]}
334
473
  />
335
- <div className="grid grid-cols-1 gap-4 md:grid-cols-4">
336
- <Card className="transition-shadow hover:shadow-md">
337
- <CardContent className="p-6">
338
- <div className="flex items-center space-x-3">
339
- <FileText className="h-10 w-10 text-blue-500" />
340
- <div>
341
- <p className="text-sm font-medium text-muted-foreground">
342
- {t('statsTotalPages')}
343
- </p>
344
- <p className="text-2xl font-bold">{stats?.total}</p>
345
- </div>
346
- </div>
347
- </CardContent>
348
- </Card>
349
-
350
- <Card className="transition-shadow hover:shadow-md">
351
- <CardContent className="p-6">
352
- <div className="flex items-center space-x-3">
353
- <Eye className="h-10 w-10 text-green-500" />
354
- <div>
355
- <p className="text-sm font-medium text-muted-foreground">
356
- {t('statsPublished')}
357
- </p>
358
- <p className="text-2xl font-bold">{stats?.totalPublished}</p>
359
- </div>
360
- </div>
361
- </CardContent>
362
- </Card>
363
-
364
- <Card className="transition-shadow hover:shadow-md">
365
- <CardContent className="p-6">
366
- <div className="flex items-center space-x-3">
367
- <Edit className="h-10 w-10 text-orange-500" />
368
- <div>
369
- <p className="text-sm font-medium text-muted-foreground">
370
- {t('statsDrafts')}
371
- </p>
372
- <p className="text-2xl font-bold">{stats?.totalDraft}</p>
373
- </div>
374
- </div>
375
- </CardContent>
376
- </Card>
377
-
378
- <Card className="transition-shadow hover:shadow-md">
379
- <CardContent className="p-6">
380
- <div className="flex items-center space-x-3">
381
- <Languages className="h-10 w-10 text-purple-500" />
382
- <div>
383
- <p className="text-sm font-medium text-muted-foreground">
384
- {t('statsActiveLanguages')}
385
- </p>
386
- <p className="text-2xl font-bold">
387
- {stats?.totalEnabledLanguages}
388
- </p>
389
- </div>
390
- </div>
391
- </CardContent>
392
- </Card>
393
- </div>
394
-
395
- <div className="relative my-4">
396
- <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
397
- <Input
398
- placeholder={t('searchPlaceholder')}
399
- value={searchTerm}
400
- onChange={(e) => handleSearchChange(e.target.value)}
401
- className="pl-10"
402
- />
403
- </div>
474
+ <StatsCards stats={statsCards} />
475
+
476
+ <SearchBar
477
+ searchQuery={searchTerm}
478
+ onSearchChange={handleSearchChange}
479
+ onSearch={() => setPage(1)}
480
+ placeholder={t('searchPlaceholder')}
481
+ controls={searchControls}
482
+ />
404
483
 
405
484
  <div className="space-y-4">
406
- {contentList.data.length > 0 ? (
407
- <div className="grid gap-4">
408
- {contentList.data.map((content: Content) => (
409
- <Card
410
- key={content.id}
411
- className="overflow-hidden transition-all duration-200 hover:border-primary/20 hover:shadow-md"
485
+ {contentList.data.length > 0 ? (
486
+ <div className="grid gap-4 md:grid-cols-2 2xl:grid-cols-3">
487
+ {contentList.data.map((content: Content) => (
488
+ <article
489
+ key={getContentIdentifier(content)}
490
+ className="group flex h-full flex-col rounded-xl border bg-card p-4 transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/30 hover:shadow-md"
412
491
  >
413
- <CardContent className="p-6">
414
- <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
415
- <div className="flex-1 space-y-3">
416
- <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
417
- <div className="space-y-1">
418
- <div className="flex items-center space-x-2">
419
- <h3 className="text-lg font-semibold">
420
- /{content.slug}
421
- </h3>
422
- {content.status && (
423
- <Badge
424
- className={cn(
425
- 'text-white',
426
- getStatusBadge(content.status)?.color ||
427
- 'bg-gray-500'
428
- )}
429
- >
430
- {getStatusBadge(content.status)?.label || 'N/A'}
431
- </Badge>
432
- )}
433
- </div>
434
- <div className="flex items-center space-x-2">
435
- {content.available_locales?.length &&
436
- content.available_locales.map((al) => (
437
- <Badge
438
- variant="outline"
439
- className="text-xs"
440
- key={al.id}
441
- >
442
- <Globe className="mr-1 h-3 w-3" />
443
- {al.code.toUpperCase()}
444
- </Badge>
445
- ))}
446
- </div>
447
- </div>
492
+ <div className="flex h-full flex-col">
493
+ <div className="space-y-3">
494
+ <div className="space-y-2">
495
+ <div className="flex flex-wrap items-center justify-between gap-2">
496
+ <h3 className="truncate text-base font-semibold text-foreground sm:text-lg">
497
+ /{content.slug}
498
+ </h3>
499
+ <Badge
500
+ className={cn(
501
+ 'text-white',
502
+ getStatusBadge(content.status)?.color ||
503
+ 'bg-gray-500'
504
+ )}
505
+ >
506
+ {getStatusBadge(content.status)?.label || 'N/A'}
507
+ </Badge>
448
508
  </div>
449
-
450
- <div className="space-y-2">
451
- <div className="flex items-center space-x-4 text-xs text-muted-foreground">
452
- <div className="flex items-center space-x-1">
453
- <Calendar className="h-3 w-3" />
454
- <span>
455
- {t('created')}{' '}
456
- {content.created_at
457
- ? formatDateTime(
458
- content.created_at,
459
- getSettingValue,
460
- currentLocaleCode
461
- )
462
- : 'N/A'}
463
- </span>
464
- </div>
465
- <div className="flex items-center space-x-1">
466
- <Calendar className="h-3 w-3" />
467
- <span>
468
- {t('updated')}{' '}
469
- {content.updated_at
470
- ? formatDateTime(
471
- content.updated_at,
472
- getSettingValue,
473
- currentLocaleCode
474
- )
475
- : 'N/A'}
476
- </span>
477
- </div>
478
- </div>
509
+ <div className="flex flex-wrap gap-2">
510
+ {content.available_locales?.map((locale) => (
511
+ <Badge
512
+ variant="outline"
513
+ className="text-xs"
514
+ key={locale.id}
515
+ >
516
+ <Globe className="mr-1 h-3 w-3" />
517
+ {locale.code.toUpperCase()}
518
+ </Badge>
519
+ ))}
479
520
  </div>
480
521
  </div>
481
522
 
482
- <div className="flex flex-wrap gap-2 lg:flex-col lg:items-end">
483
- <Button
484
- variant="outline"
485
- size="sm"
486
- onClick={() => handleEditContent(content)}
487
- className="transition-colors hover:border-blue-200 hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-950"
488
- >
489
- <Edit className="mr-1 h-4 w-4" />
490
- {t('buttonEdit')}
491
- </Button>
523
+ <div className="space-y-2">
524
+ {content.title && (
525
+ <p className="line-clamp-1 text-sm font-medium text-foreground">
526
+ {content.title}
527
+ </p>
528
+ )}
529
+ <p className="line-clamp-3 min-h-[60px] text-sm text-muted-foreground">
530
+ {content.body?.replace(/<[^>]*>/g, ' ').trim() ||
531
+ t('emptyDescription')}
532
+ </p>
533
+ </div>
492
534
 
493
- <AlertDialog>
494
- <AlertDialogTrigger asChild>
495
- <Button
496
- variant="outline"
497
- size="sm"
498
- className="bg-transparent transition-colors hover:border-red-200 hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-950"
499
- >
500
- <Trash2 className="mr-1 h-4 w-4" />
501
- {t('buttonDelete')}
502
- </Button>
503
- </AlertDialogTrigger>
504
- <AlertDialogContent>
505
- <AlertDialogHeader>
506
- <AlertDialogTitle>
507
- {t('alertDeleteTitle')}
508
- </AlertDialogTitle>
509
- <AlertDialogDescription>
510
- {t('alertDeleteDescription', {
511
- slug: content.slug,
512
- })}
513
- </AlertDialogDescription>
514
- </AlertDialogHeader>
515
- <AlertDialogFooter>
516
- <AlertDialogCancel>
517
- {t('alertDeleteCancel')}
518
- </AlertDialogCancel>
519
- <AlertDialogAction
520
- onClick={() => {
521
- if (content.id) {
522
- handleDeleteContent(
523
- content.content_id || content.id
524
- );
525
- }
526
- }}
527
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
528
- >
529
- {t('alertDeleteConfirm')}
530
- </AlertDialogAction>
531
- </AlertDialogFooter>
532
- </AlertDialogContent>
533
- </AlertDialog>
535
+ <div className="grid gap-2 rounded-lg bg-muted/40 p-3 text-xs text-muted-foreground">
536
+ <div className="flex items-center gap-1">
537
+ <Calendar className="h-3 w-3" />
538
+ <span>
539
+ {t('created')}{' '}
540
+ {content.created_at
541
+ ? formatDateTime(
542
+ content.created_at,
543
+ getSettingValue,
544
+ currentLocaleCode
545
+ )
546
+ : 'N/A'}
547
+ </span>
548
+ </div>
549
+ <div className="flex items-center gap-1">
550
+ <Calendar className="h-3 w-3" />
551
+ <span>
552
+ {t('updated')}{' '}
553
+ {content.updated_at
554
+ ? formatDateTime(
555
+ content.updated_at,
556
+ getSettingValue,
557
+ currentLocaleCode
558
+ )
559
+ : 'N/A'}
560
+ </span>
561
+ </div>
534
562
  </div>
535
563
  </div>
536
- </CardContent>
537
- </Card>
564
+
565
+ <div className="mt-4 flex flex-wrap gap-2 border-t pt-4">
566
+ <Button
567
+ variant="outline"
568
+ size="sm"
569
+ className="flex-1"
570
+ onClick={() => void handleEditContent(content)}
571
+ >
572
+ <Edit className="mr-1 h-4 w-4" />
573
+ {t('buttonEdit')}
574
+ </Button>
575
+ <Button
576
+ variant="outline"
577
+ size="sm"
578
+ className="flex-1 bg-transparent text-destructive hover:bg-destructive/10 hover:text-destructive"
579
+ onClick={() => setDeleteTarget(content)}
580
+ >
581
+ <Trash2 className="mr-1 h-4 w-4" />
582
+ {t('buttonDelete')}
583
+ </Button>
584
+ </div>
585
+ </div>
586
+ </article>
538
587
  ))}
539
588
  </div>
540
589
  ) : (
541
- <Card>
542
- <CardContent className="p-12 text-center">
543
- <div className="flex flex-col items-center space-y-4">
544
- <FileText className="h-12 w-12 text-muted-foreground" />
545
- <div>
546
- <h3 className="text-lg font-semibold">{t('emptyTitle')}</h3>
547
- <p className="text-muted-foreground">
548
- {t('emptyDescription')}
549
- </p>
550
- </div>
551
- <Button onClick={handleNewContent}>
552
- <Plus className="mr-2 h-4 w-4" />
553
- {t('emptyButtonCreate')}
554
- </Button>
555
- </div>
556
- </CardContent>
557
- </Card>
590
+ <EmptyState
591
+ icon={<FileText className="h-12 w-12" />}
592
+ title={t('emptyTitle')}
593
+ description={t('emptyDescription')}
594
+ actionLabel={t('emptyButtonCreate')}
595
+ onAction={handleNewContent}
596
+ actionIcon={<Plus className="mr-2 h-4 w-4" />}
597
+ />
558
598
  )}
559
599
  </div>
560
- <Dialog open={isFormDialogOpen} onOpenChange={setIsFormDialogOpen}>
561
- <DialogContent className="max-h-[90vh] max-w-4xl overflow-y-auto">
562
- <DialogHeader>
563
- <DialogTitle>
600
+
601
+ <PaginationFooter
602
+ currentPage={currentPage}
603
+ pageSize={contentList.pageSize || pageSize}
604
+ totalItems={contentList.total}
605
+ onPageChange={setPage}
606
+ onPageSizeChange={(nextPageSize) => {
607
+ setPageSize(nextPageSize);
608
+ setPage(1);
609
+ }}
610
+ />
611
+
612
+ <Sheet
613
+ open={isFormSheetOpen}
614
+ onOpenChange={(open) => {
615
+ setIsFormSheetOpen(open);
616
+ if (!open) {
617
+ persistSelectedLocale();
618
+ }
619
+ }}
620
+ >
621
+ <SheetContent className="w-full overflow-y-auto sm:max-w-2xl">
622
+ <SheetHeader>
623
+ <SheetTitle>
564
624
  {editingContentId
565
625
  ? t('dialogFormTitleEdit')
566
626
  : t('dialogFormTitleCreate')}
567
- </DialogTitle>
568
- <DialogDescription>
627
+ </SheetTitle>
628
+ <SheetDescription>
569
629
  {editingContentId
570
630
  ? t('dialogFormDescriptionEdit')
571
631
  : t('dialogFormDescriptionCreate')}
572
- </DialogDescription>
573
- </DialogHeader>
632
+ </SheetDescription>
633
+ </SheetHeader>
574
634
 
575
635
  <Form {...form}>
576
- <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
636
+ <form
637
+ onSubmit={form.handleSubmit(onSubmit)}
638
+ className="space-y-6 px-4 py-4"
639
+ >
577
640
  <div className="grid gap-4 sm:grid-cols-2">
578
641
  <FormField
579
642
  control={form.control}
@@ -625,50 +688,28 @@ export default function ContentsPage() {
625
688
  />
626
689
  </div>
627
690
 
628
- {editingContentId && (
629
- <div className="space-y-2">
630
- <Label>{t('formLabelLanguage')}</Label>
631
- <Select
632
- value={selectedLocale}
633
- onValueChange={(newLocale) => {
634
- const currentValues = form.getValues();
635
- const localesData = (form as any).localesData || {};
636
-
637
- if (selectedLocale) {
638
- localesData[selectedLocale] = {
639
- title: currentValues.title,
640
- body: currentValues.body,
641
- };
642
- }
643
-
644
- const newLocaleData = localesData[newLocale] || {
645
- title: '',
646
- body: '',
647
- };
648
-
649
- form.setValue('title', newLocaleData.title);
650
- form.setValue('body', newLocaleData.body);
651
- (form as any).localesData = localesData;
652
- setSelectedLocale(newLocale);
653
- }}
654
- disabled={isSubmitting}
655
- >
656
- <SelectTrigger className="w-full">
657
- <SelectValue placeholder={t('formPlaceholderLanguage')} />
658
- </SelectTrigger>
659
- <SelectContent>
660
- {locales.map((locale) => (
661
- <SelectItem key={locale.code} value={locale.code}>
662
- <div className="flex items-center">
663
- <Globe className="mr-2 h-4 w-4" />
664
- {locale.name}
665
- </div>
666
- </SelectItem>
667
- ))}
668
- </SelectContent>
669
- </Select>
670
- </div>
671
- )}
691
+ <div className="space-y-2">
692
+ <Label>{t('formLabelLanguage')}</Label>
693
+ <Select
694
+ value={selectedLocale}
695
+ onValueChange={handleLocaleChange}
696
+ disabled={isSubmitting}
697
+ >
698
+ <SelectTrigger className="w-full">
699
+ <SelectValue placeholder={t('formPlaceholderLanguage')} />
700
+ </SelectTrigger>
701
+ <SelectContent>
702
+ {locales.map((locale) => (
703
+ <SelectItem key={locale.code} value={locale.code}>
704
+ <div className="flex items-center">
705
+ <Globe className="mr-2 h-4 w-4" />
706
+ {locale.name}
707
+ </div>
708
+ </SelectItem>
709
+ ))}
710
+ </SelectContent>
711
+ </Select>
712
+ </div>
672
713
 
673
714
  <div className="space-y-4">
674
715
  <FormField
@@ -711,8 +752,18 @@ export default function ContentsPage() {
711
752
  ?.name || '',
712
753
  })}
713
754
  </FormLabel>
755
+ <p className="text-sm text-muted-foreground">
756
+ {t('formPlaceholderContent', {
757
+ language:
758
+ locales.find((l) => l.code === selectedLocale)
759
+ ?.name || '',
760
+ })}
761
+ </p>
714
762
  <FormControl>
715
- <RichTextEditor className="max-w-[845px]" {...field} />
763
+ <RichTextEditor
764
+ className="w-full max-w-full"
765
+ {...field}
766
+ />
716
767
  </FormControl>
717
768
  <FormMessage />
718
769
  </FormItem>
@@ -720,28 +771,43 @@ export default function ContentsPage() {
720
771
  />
721
772
  </div>
722
773
 
723
- <DialogFooter>
724
- <Button
725
- type="button"
726
- variant="outline"
727
- onClick={() => setIsFormDialogOpen(false)}
728
- disabled={isSubmitting}
729
- >
730
- {t('formButtonCancel')}
731
- </Button>
732
- <Button type="submit" disabled={isSubmitting}>
733
- {isSubmitting && (
734
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
735
- )}
736
- {editingContentId
737
- ? t('formButtonUpdate')
738
- : t('formButtonCreate')}
739
- </Button>
740
- </DialogFooter>
774
+ <Button className="w-full" type="submit" disabled={isSubmitting}>
775
+ {isSubmitting && (
776
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
777
+ )}
778
+ {editingContentId
779
+ ? t('formButtonUpdate')
780
+ : t('formButtonCreate')}
781
+ </Button>
741
782
  </form>
742
783
  </Form>
743
- </DialogContent>
744
- </Dialog>
745
- </div>
784
+ </SheetContent>
785
+ </Sheet>
786
+
787
+ <AlertDialog
788
+ open={deleteTarget !== null}
789
+ onOpenChange={(open) => !open && setDeleteTarget(null)}
790
+ >
791
+ <AlertDialogContent>
792
+ <AlertDialogHeader>
793
+ <AlertDialogTitle>{t('alertDeleteTitle')}</AlertDialogTitle>
794
+ <AlertDialogDescription>
795
+ {deleteTarget
796
+ ? t('alertDeleteDescription', { slug: deleteTarget.slug })
797
+ : ''}
798
+ </AlertDialogDescription>
799
+ </AlertDialogHeader>
800
+ <AlertDialogFooter>
801
+ <AlertDialogCancel>{t('alertDeleteCancel')}</AlertDialogCancel>
802
+ <AlertDialogAction
803
+ onClick={() => void handleDeleteContent()}
804
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
805
+ >
806
+ {t('alertDeleteConfirm')}
807
+ </AlertDialogAction>
808
+ </AlertDialogFooter>
809
+ </AlertDialogContent>
810
+ </AlertDialog>
811
+ </Page>
746
812
  );
747
813
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/content",
3
- "version": "0.0.279",
3
+ "version": "0.0.285",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,11 +9,11 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/api-locale": "0.0.13",
13
12
  "@hed-hog/api-pagination": "0.0.6",
14
- "@hed-hog/core": "0.0.279",
15
- "@hed-hog/api": "0.0.4",
16
- "@hed-hog/api-prisma": "0.0.5"
13
+ "@hed-hog/api-locale": "0.0.13",
14
+ "@hed-hog/core": "0.0.285",
15
+ "@hed-hog/api-prisma": "0.0.5",
16
+ "@hed-hog/api": "0.0.4"
17
17
  },
18
18
  "exports": {
19
19
  ".": {
@@ -232,9 +232,9 @@ export class ContentService {
232
232
  throw new BadRequestException(getLocaleText('localeNotFound', localeStr, `Locale ${localeCode} not found`).replace('{{locale}}', localeCode));
233
233
  }
234
234
 
235
- const existingLocale = await this.prisma.category_locale.findFirst({
235
+ const existingLocale = await this.prisma.content_locale.findFirst({
236
236
  where: {
237
- category_id: Number(id),
237
+ content_id: Number(id),
238
238
  locale_id: localeRecord.id,
239
239
  },
240
240
  });