@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.
- package/hedhog/frontend/app/page.tsx.ejs +139 -163
- package/package.json +4 -4
|
@@ -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
|
-
<
|
|
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
|
-
<
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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="
|
|
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-
|
|
307
|
-
<div className="flex items-start
|
|
308
|
-
<div className="
|
|
309
|
-
<
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
<
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
</
|
|
364
|
-
</
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
<
|
|
395
|
-
<
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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={
|
|
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
|
-
<
|
|
424
|
-
<
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
</
|
|
432
|
-
<
|
|
407
|
+
</SheetTitle>
|
|
408
|
+
<SheetDescription>
|
|
433
409
|
{isNewFAQ ? t('createDescription') : t('editDescription')}
|
|
434
|
-
</
|
|
435
|
-
</
|
|
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
|
-
<
|
|
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
|
-
</
|
|
542
|
-
</
|
|
543
|
-
</
|
|
544
|
-
</
|
|
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.
|
|
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-
|
|
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
|
".": {
|