@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.
- package/hedhog/frontend/app/page.tsx.ejs +138 -168
- package/package.json +3 -3
|
@@ -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
|
-
|
|
106
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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="
|
|
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-
|
|
313
|
-
<div className="flex items-start
|
|
314
|
-
<div className="
|
|
315
|
-
<
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
<
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
</
|
|
370
|
-
</
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
<
|
|
401
|
-
<
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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={
|
|
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
|
-
<
|
|
430
|
-
<
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
</
|
|
438
|
-
<
|
|
407
|
+
</SheetTitle>
|
|
408
|
+
<SheetDescription>
|
|
439
409
|
{isNewFAQ ? t('createDescription') : t('editDescription')}
|
|
440
|
-
</
|
|
441
|
-
</
|
|
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
|
-
<
|
|
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
|
-
</
|
|
548
|
-
</
|
|
549
|
-
</
|
|
550
|
-
</
|
|
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.
|
|
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.
|
|
16
|
-
"@hed-hog/api": "0.0.4"
|
|
16
|
+
"@hed-hog/core": "0.0.285"
|
|
17
17
|
},
|
|
18
18
|
"exports": {
|
|
19
19
|
".": {
|