@hed-hog/faq 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.
@@ -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,
@@ -19,14 +22,6 @@ import {
19
22
  import { Badge } from '@/components/ui/badge';
20
23
  import { Button } from '@/components/ui/button';
21
24
  import { Card, CardContent } from '@/components/ui/card';
22
- import {
23
- Dialog,
24
- DialogContent,
25
- DialogDescription,
26
- DialogFooter,
27
- DialogHeader,
28
- DialogTitle,
29
- } from '@/components/ui/dialog';
30
25
  import { Input } from '@/components/ui/input';
31
26
  import { Label } from '@/components/ui/label';
32
27
  import {
@@ -36,6 +31,13 @@ import {
36
31
  SelectTrigger,
37
32
  SelectValue,
38
33
  } from '@/components/ui/select';
34
+ import {
35
+ Sheet,
36
+ SheetContent,
37
+ SheetDescription,
38
+ SheetHeader,
39
+ SheetTitle,
40
+ } from '@/components/ui/sheet';
39
41
  import { Textarea } from '@/components/ui/textarea';
40
42
  import { useDebounce } from '@/hooks/use-debounce';
41
43
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
@@ -84,7 +86,6 @@ type FaqDetail = {
84
86
 
85
87
  export default function FAQPage() {
86
88
  const t = useTranslations('faq.Faq');
87
- const [faqs, setFAQs] = useState<any[]>([]);
88
89
  const [selectedFAQ, setSelectedFAQ] = useState<any | null>(null);
89
90
  const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
90
91
  const [isNewFAQ, setIsNewFAQ] = useState(false);
@@ -101,9 +102,9 @@ export default function FAQPage() {
101
102
  }
102
103
  }, [currentLocaleCode]);
103
104
 
104
- const { data: faqResult, refetch: refetchFaq } = useQuery<
105
- PaginationResult<Faq>
106
- >({
105
+ const { data: faqResult, refetch: refetchFaq } = useQuery<
106
+ PaginationResult<Faq>
107
+ >({
107
108
  queryKey: ['faq', debouncedSearch, page, pageSize],
108
109
  queryFn: async () => {
109
110
  const response = await request({
@@ -116,8 +117,8 @@ export default function FAQPage() {
116
117
  });
117
118
  return response.data as PaginationResult<Faq>;
118
119
  },
119
- });
120
- const { data = [], total = 0 } = faqResult ?? {};
120
+ });
121
+ const { data: faqs = [], total = 0 } = faqResult ?? {};
121
122
 
122
123
  const { data: statsData, refetch: refetchStats } = useQuery<any>({
123
124
  queryKey: ['faq-stats'],
@@ -129,12 +130,6 @@ export default function FAQPage() {
129
130
  },
130
131
  });
131
132
 
132
- useEffect(() => {
133
- if (data) {
134
- setFAQs(data);
135
- }
136
- }, [data]);
137
-
138
133
  const handleNewFAQ = (): void => {
139
134
  const newFAQ: any = {
140
135
  locale: {},
@@ -246,15 +241,11 @@ export default function FAQPage() {
246
241
 
247
242
  const handleSearchChange = (value: string): void => {
248
243
  setSearchTerm(value);
244
+ setPage(1);
249
245
  };
250
246
 
251
- useEffect(() => {
252
- refetchFaq();
253
- refetchStats();
254
- }, [isEditDialogOpen, debouncedSearch, page, pageSize]);
255
-
256
247
  return (
257
- <div className="flex flex-col h-screen px-4">
248
+ <Page>
258
249
  <PageHeader
259
250
  breadcrumbs={[{ label: 'Home', href: '/' }, { label: t('title') }]}
260
251
  actions={[
@@ -267,147 +258,126 @@ export default function FAQPage() {
267
258
  title={t('title')}
268
259
  description={t('description')}
269
260
  />
270
- <div className="grid grid-cols-1 gap-4 md:grid-cols-4">
271
- <Card className="transition-shadow hover:shadow-md p-2">
272
- <CardContent className="p-4">
273
- <div className="flex items-center space-x-3">
274
- <div className="rounded-full bg-blue-100 p-2 dark:bg-blue-900">
275
- <HelpCircle className="h-6 w-6 text-blue-600 dark:text-blue-400" />
276
- </div>
277
- <div>
278
- <p className="text-sm font-medium text-muted-foreground">
279
- {t('totalFaqs')}
280
- </p>
281
- <p className="text-2xl font-bold">{statsData?.total}</p>
282
- </div>
283
- </div>
284
- </CardContent>
285
- </Card>
286
- </div>
287
261
 
288
- <div className="mb-4 flex flex-col gap-4 md:flex-row mt-4">
289
- <SearchBar
290
- searchQuery={searchTerm}
291
- onSearchChange={handleSearchChange}
292
- onSearch={() => refetchFaq()}
293
- placeholder={t('searchPlaceholder')}
294
- />
295
- </div>
262
+ <StatsCards
263
+ stats={[
264
+ {
265
+ title: t('totalFaqs'),
266
+ value: String(statsData?.total || 0),
267
+ icon: <HelpCircle className="h-5 w-5" />,
268
+ iconBgColor: 'bg-blue-100 dark:bg-blue-900',
269
+ iconColor: 'text-blue-600 dark:text-blue-400',
270
+ },
271
+ ]}
272
+ className="grid-cols-1 md:grid-cols-4"
273
+ />
274
+
275
+ <SearchBar
276
+ searchQuery={searchTerm}
277
+ onSearchChange={handleSearchChange}
278
+ onSearch={() => setPage(1)}
279
+ placeholder={t('searchPlaceholder')}
280
+ />
296
281
 
297
282
  <div className="space-y-4">
298
283
  {faqs.length > 0 ? (
299
- <div className="space-y-4">
300
- {faqs.map((faq) => (
284
+ <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
285
+ {faqs.map((faq, index) => (
301
286
  <Card
302
- key={faq.id}
287
+ key={`${faq.faq_id ?? faq.id ?? 'faq'}-${index}`}
303
288
  onDoubleClick={() => handleEditFAQ(faq)}
304
289
  className="cursor-pointer transition-all duration-200 hover:border-primary/20 hover:shadow-md"
305
290
  >
306
- <CardContent className="p-6">
307
- <div className="flex items-start justify-between gap-4">
308
- <div className="flex-1 space-y-3">
309
- <div className="flex items-start space-x-3">
310
- <div className="mt-1 rounded-full bg-primary/10 p-2">
311
- <HelpCircle className="h-4 w-4 text-primary" />
312
- </div>
313
- <div className="flex-1 space-y-2">
314
- <div className="flex items-center gap-2 flex-wrap">
315
- <h3 className="text-lg font-semibold leading-tight">
316
- {faq.question}
317
- </h3>
318
- {faq.available_locales &&
319
- faq.available_locales.length > 0 && (
320
- <div className="flex gap-1 flex-wrap">
321
- {faq.available_locales.map(
322
- (locale: Locale) => (
323
- <Badge
324
- key={locale.code}
325
- variant="outline"
326
- className="text-xs"
327
- >
328
- <Globe className="mr-1 h-3 w-3" />
329
- {locale.code.toUpperCase()}
330
- </Badge>
331
- )
332
- )}
333
- </div>
334
- )}
335
- </div>
336
- <p className="line-clamp-2 text-sm text-muted-foreground">
337
- {faq.answer}
338
- </p>
339
- </div>
291
+ <CardContent className="p-4">
292
+ <div className="flex items-start gap-3">
293
+ <div className="mt-0.5 rounded-full bg-primary/10 p-2">
294
+ <HelpCircle className="h-4 w-4 text-primary" />
295
+ </div>
296
+ <div className="min-w-0 flex-1 space-y-2">
297
+ <div className="flex items-start gap-2 flex-wrap">
298
+ <h3 className="line-clamp-2 text-base font-semibold leading-tight">
299
+ {faq.question}
300
+ </h3>
340
301
  </div>
302
+
303
+ {faq.available_locales &&
304
+ faq.available_locales.length > 0 && (
305
+ <div className="flex gap-1 flex-wrap">
306
+ {faq.available_locales.map((locale: Locale) => (
307
+ <Badge
308
+ key={locale.code}
309
+ variant="outline"
310
+ className="text-xs"
311
+ >
312
+ <Globe className="mr-1 h-3 w-3" />
313
+ {locale.code.toUpperCase()}
314
+ </Badge>
315
+ ))}
316
+ </div>
317
+ )}
318
+
319
+ <p className="line-clamp-3 text-sm text-muted-foreground">
320
+ {faq.answer}
321
+ </p>
341
322
  </div>
323
+ </div>
342
324
 
343
- <div className="flex flex-col gap-2">
344
- <Button
345
- variant="outline"
346
- size="sm"
347
- onClick={() => handleEditFAQ(faq)}
348
- className="transition-colors hover:border-blue-200 hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-950"
349
- >
350
- <Edit className="mr-1 h-4 w-4" />
351
- {t('edit')}
352
- </Button>
353
-
354
- <AlertDialog>
355
- <AlertDialogTrigger asChild>
356
- <Button
357
- variant="outline"
358
- size="sm"
359
- className="bg-transparent transition-colors hover:border-red-200 hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-950"
325
+ <div className="mt-4 flex items-center justify-end gap-2">
326
+ <Button
327
+ variant="outline"
328
+ size="sm"
329
+ onClick={() => handleEditFAQ(faq)}
330
+ className="transition-colors hover:border-blue-200 hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-950"
331
+ >
332
+ <Edit className="mr-1 h-4 w-4" />
333
+ {t('edit')}
334
+ </Button>
335
+
336
+ <AlertDialog>
337
+ <AlertDialogTrigger asChild>
338
+ <Button
339
+ variant="outline"
340
+ size="sm"
341
+ className="bg-transparent transition-colors hover:border-red-200 hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-950"
342
+ >
343
+ <Trash2 className="mr-1 h-4 w-4" />
344
+ {t('delete')}
345
+ </Button>
346
+ </AlertDialogTrigger>
347
+ <AlertDialogContent>
348
+ <AlertDialogHeader>
349
+ <AlertDialogTitle>
350
+ {t('confirmDelete')}
351
+ </AlertDialogTitle>
352
+ <AlertDialogDescription>
353
+ {t('deleteDescription')}
354
+ </AlertDialogDescription>
355
+ </AlertDialogHeader>
356
+ <AlertDialogFooter>
357
+ <AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
358
+ <AlertDialogAction
359
+ onClick={() => handleDeleteFAQ(Number(faq.faq_id))}
360
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
360
361
  >
361
- <Trash2 className="mr-1 h-4 w-4" />
362
362
  {t('delete')}
363
- </Button>
364
- </AlertDialogTrigger>
365
- <AlertDialogContent>
366
- <AlertDialogHeader>
367
- <AlertDialogTitle>
368
- {t('confirmDelete')}
369
- </AlertDialogTitle>
370
- <AlertDialogDescription>
371
- {t('deleteDescription')}
372
- </AlertDialogDescription>
373
- </AlertDialogHeader>
374
- <AlertDialogFooter>
375
- <AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
376
- <AlertDialogAction
377
- onClick={() =>
378
- handleDeleteFAQ(Number(faq.faq_id))
379
- }
380
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
381
- >
382
- {t('delete')}
383
- </AlertDialogAction>
384
- </AlertDialogFooter>
385
- </AlertDialogContent>
386
- </AlertDialog>
387
- </div>
363
+ </AlertDialogAction>
364
+ </AlertDialogFooter>
365
+ </AlertDialogContent>
366
+ </AlertDialog>
388
367
  </div>
389
368
  </CardContent>
390
369
  </Card>
391
370
  ))}
392
371
  </div>
393
372
  ) : (
394
- <Card>
395
- <CardContent className="p-12 text-center">
396
- <div className="flex flex-col items-center space-y-4">
397
- <HelpCircle className="h-12 w-12 text-muted-foreground" />
398
- <div>
399
- <h3 className="text-lg font-semibold">
400
- {t('noQuestionsFound')}
401
- </h3>
402
- <p className="text-muted-foreground">{t('adjustFilters')}</p>
403
- </div>
404
- <Button onClick={handleNewFAQ}>
405
- <Plus className="mr-2 h-4 w-4" />
406
- {t('createFirstQuestion')}
407
- </Button>
408
- </div>
409
- </CardContent>
410
- </Card>
373
+ <EmptyState
374
+ icon={<HelpCircle className="h-12 w-12" />}
375
+ title={t('noQuestionsFound')}
376
+ description={t('adjustFilters')}
377
+ actionLabel={t('createFirstQuestion')}
378
+ actionIcon={<Plus className="mr-2 h-4 w-4" />}
379
+ onAction={handleNewFAQ}
380
+ />
411
381
  )}
412
382
 
413
383
  <PaginationFooter
@@ -415,27 +385,33 @@ export default function FAQPage() {
415
385
  pageSize={pageSize}
416
386
  totalItems={total}
417
387
  onPageChange={setPage}
418
- onPageSizeChange={setPageSize}
388
+ onPageSizeChange={(nextPageSize) => {
389
+ setPageSize(nextPageSize);
390
+ setPage(1);
391
+ }}
419
392
  pageSizeOptions={[10, 20, 30, 40, 50]}
420
393
  />
421
394
  </div>
422
395
 
423
- <Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
424
- <DialogContent className="max-h-[95vh] max-w-4xl overflow-y-auto">
425
- <DialogHeader>
426
- <DialogTitle className="flex items-center space-x-2">
396
+ <Sheet open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
397
+ <SheetContent
398
+ side="right"
399
+ className="max-h-[95vh] w-full max-w-5xl overflow-y-auto"
400
+ >
401
+ <SheetHeader>
402
+ <SheetTitle className="flex items-center space-x-2">
427
403
  <Edit className="h-5 w-5" />
428
404
  <span>
429
405
  {isNewFAQ ? t('newQuestionTitle') : t('editQuestion')}
430
406
  </span>
431
- </DialogTitle>
432
- <DialogDescription>
407
+ </SheetTitle>
408
+ <SheetDescription>
433
409
  {isNewFAQ ? t('createDescription') : t('editDescription')}
434
- </DialogDescription>
435
- </DialogHeader>
410
+ </SheetDescription>
411
+ </SheetHeader>
436
412
 
437
413
  {selectedFAQ && (
438
- <div className="space-y-6">
414
+ <div className="space-y-6 px-4">
439
415
  {isEditDialogOpen && !isNewFAQ && (
440
416
  <div className="space-y-2">
441
417
  <Label
@@ -513,7 +489,7 @@ export default function FAQPage() {
513
489
  </div>
514
490
  )}
515
491
 
516
- <DialogFooter className="mt-4">
492
+ <div className="mt-4 flex justify-end gap-2">
517
493
  <Button
518
494
  variant="outline"
519
495
  onClick={() => {
@@ -538,9 +514,9 @@ export default function FAQPage() {
538
514
  <Save className="mr-2 h-4 w-4" />
539
515
  {isNewFAQ ? t('createQuestionButton') : t('saveChanges')}
540
516
  </Button>
541
- </DialogFooter>
542
- </DialogContent>
543
- </Dialog>
544
- </div>
517
+ </div>
518
+ </SheetContent>
519
+ </Sheet>
520
+ </Page>
545
521
  );
546
522
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/faq",
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": {
@@ -10,10 +10,10 @@
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
12
  "@hed-hog/api-locale": "0.0.13",
13
- "@hed-hog/api-prisma": "0.0.5",
14
- "@hed-hog/core": "0.0.279",
15
13
  "@hed-hog/api": "0.0.4",
16
- "@hed-hog/api-pagination": "0.0.6"
14
+ "@hed-hog/api-prisma": "0.0.5",
15
+ "@hed-hog/api-pagination": "0.0.6",
16
+ "@hed-hog/core": "0.0.285"
17
17
  },
18
18
  "exports": {
19
19
  ".": {