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