From 0b38de74a2ed52afbd7c8af66dff1ccd14c5be17 Mon Sep 17 00:00:00 2001 From: louiscklaw Date: Tue, 22 Apr 2025 04:18:53 +0800 Subject: [PATCH] update refactoring, --- .../src/app/dashboard/lp/categories/page.tsx | 2 +- .../categories/[cat_id]/BasicDetailCard.tsx | 4 +- .../mf/categories/[cat_id]/TitleCard.tsx | 6 +- .../dashboard/mf/categories/[cat_id]/page.tsx | 18 +- .../dashboard/mf/categories/create/page.tsx | 6 +- .../mf/categories/edit/[cat_id]/page.tsx | 6 +- .../src/app/dashboard/mf/categories/page.tsx | 43 +- .../mf/questions/[cat_id]/BasicDetailCard.tsx | 4 +- .../mf/questions/[cat_id]/TitleCard.tsx | 6 +- .../dashboard/mf/questions/[cat_id]/page.tsx | 14 +- .../dashboard/mf/questions/create/page.tsx | 4 +- .../mf/questions/edit/[cat_id]/page.tsx | 4 +- .../src/app/dashboard/mf/questions/page.tsx | 40 +- .../categories/mf-categories-filters.tsx} | 0 .../categories/mf-categories-pagination.tsx} | 0 .../mf-categories-selection-context.tsx} | 0 .../categories/mf-categories-table.tsx} | 0 .../categories/mf-category-create-form.tsx} | 2 +- .../categories/mf-category-edit-form.tsx} | 0 .../dashboard/mf/categories/_constants.ts | 8 +- .../mf/categories/lp-category-create-form.tsx | 8 +- .../mf/categories/mf-categories-filters.tsx | 456 ++++++++++++++++ .../categories/mf-categories-pagination.tsx | 49 ++ .../mf-categories-selection-context.tsx | 46 ++ .../mf/categories/mf-categories-table.tsx | 241 +++++++++ .../mf-category-edit-form.tsx} | 12 +- .../dashboard/mf/categories/type.d.ts | 2 +- .../dashboard/mf/questions/_constants.ts | 8 +- .../mf/questions/mf-question-create-form.tsx | 419 +++++++++++++++ .../mf/questions/mf-question-edit-form.tsx | 500 ++++++++++++++++++ ...s-filters.tsx => mf-questions-filters.tsx} | 12 +- ...nation.tsx => mf-questions-pagination.tsx} | 2 +- ...tsx => mf-questions-selection-context.tsx} | 10 +- ...tions-table.tsx => mf-questions-table.tsx} | 16 +- .../dashboard/mf/questions/type.d.ts | 2 +- 002_source/cms/src/constants.ts | 2 + 36 files changed, 1833 insertions(+), 119 deletions(-) rename 002_source/cms/src/components/dashboard/{mf/categories/lp-categories-filters.tsx => lp/categories/mf-categories-filters.tsx} (100%) rename 002_source/cms/src/components/dashboard/{mf/categories/lp-categories-pagination.tsx => lp/categories/mf-categories-pagination.tsx} (100%) rename 002_source/cms/src/components/dashboard/{mf/categories/lp-categories-selection-context.tsx => lp/categories/mf-categories-selection-context.tsx} (100%) rename 002_source/cms/src/components/dashboard/{mf/categories/lp-categories-table.tsx => lp/categories/mf-categories-table.tsx} (100%) rename 002_source/cms/src/components/dashboard/{mf/questions/lp-question-create-form.tsx => lp/categories/mf-category-create-form.tsx} (99%) rename 002_source/cms/src/components/dashboard/{mf/categories/lp-category-edit-form.tsx => lp/categories/mf-category-edit-form.tsx} (100%) create mode 100644 002_source/cms/src/components/dashboard/mf/categories/mf-categories-filters.tsx create mode 100644 002_source/cms/src/components/dashboard/mf/categories/mf-categories-pagination.tsx create mode 100644 002_source/cms/src/components/dashboard/mf/categories/mf-categories-selection-context.tsx create mode 100644 002_source/cms/src/components/dashboard/mf/categories/mf-categories-table.tsx rename 002_source/cms/src/components/dashboard/mf/{questions/lp-question-edit-form.tsx => categories/mf-category-edit-form.tsx} (97%) create mode 100644 002_source/cms/src/components/dashboard/mf/questions/mf-question-create-form.tsx create mode 100644 002_source/cms/src/components/dashboard/mf/questions/mf-question-edit-form.tsx rename 002_source/cms/src/components/dashboard/mf/questions/{lp-questions-filters.tsx => mf-questions-filters.tsx} (97%) rename 002_source/cms/src/components/dashboard/mf/questions/{lp-questions-pagination.tsx => mf-questions-pagination.tsx} (96%) rename 002_source/cms/src/components/dashboard/mf/questions/{lp-questions-selection-context.tsx => mf-questions-selection-context.tsx} (80%) rename 002_source/cms/src/components/dashboard/mf/questions/{lp-questions-table.tsx => mf-questions-table.tsx} (94%) diff --git a/002_source/cms/src/app/dashboard/lp/categories/page.tsx b/002_source/cms/src/app/dashboard/lp/categories/page.tsx index 24a908c..05e4b60 100644 --- a/002_source/cms/src/app/dashboard/lp/categories/page.tsx +++ b/002_source/cms/src/app/dashboard/lp/categories/page.tsx @@ -57,7 +57,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { try { const models: ListResult = await pb .collection(COL_QUIZ_LP_CATEGORIES) - .getList(currentPage + 1, rowsPerPage, {}); + .getList(currentPage + 1, rowsPerPage, listOption); const { items, totalItems } = models; const tempLessonTypes: LpCategory[] = items.map((lt) => { return { ...defaultLpCategory, ...lt }; diff --git a/002_source/cms/src/app/dashboard/mf/categories/[cat_id]/BasicDetailCard.tsx b/002_source/cms/src/app/dashboard/mf/categories/[cat_id]/BasicDetailCard.tsx index 6683056..1e885c3 100644 --- a/002_source/cms/src/app/dashboard/mf/categories/[cat_id]/BasicDetailCard.tsx +++ b/002_source/cms/src/app/dashboard/mf/categories/[cat_id]/BasicDetailCard.tsx @@ -13,13 +13,13 @@ import { useTranslation } from 'react-i18next'; import { PropertyItem } from '@/components/core/property-item'; import { PropertyList } from '@/components/core/property-list'; -import { LpCategory } from '@/components/dashboard/lp/categories/type'; +import type { MfCategory } from '@/components/dashboard/mf/categories/type'; export default function BasicDetailCard({ lpModel: model, handleEditClick, }: { - lpModel: LpCategory; + lpModel: MfCategory; handleEditClick: () => void; }): React.JSX.Element { const { t } = useTranslation(); diff --git a/002_source/cms/src/app/dashboard/mf/categories/[cat_id]/TitleCard.tsx b/002_source/cms/src/app/dashboard/mf/categories/[cat_id]/TitleCard.tsx index 6e38fb6..059baaf 100644 --- a/002_source/cms/src/app/dashboard/mf/categories/[cat_id]/TitleCard.tsx +++ b/002_source/cms/src/app/dashboard/mf/categories/[cat_id]/TitleCard.tsx @@ -10,13 +10,13 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { useTranslation } from 'react-i18next'; -import { LpCategory } from '@/components/dashboard/lp/categories/type'; +import type { MfCategory } from '@/components/dashboard/mf/categories/type'; -function getImageUrlFrRecord(record: LpCategory): string { +function getImageUrlFrRecord(record: MfCategory): string { return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`; } -export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element { +export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element { const { t } = useTranslation(); return ( diff --git a/002_source/cms/src/app/dashboard/mf/categories/[cat_id]/page.tsx b/002_source/cms/src/app/dashboard/mf/categories/[cat_id]/page.tsx index 578262a..4e3859f 100644 --- a/002_source/cms/src/app/dashboard/mf/categories/[cat_id]/page.tsx +++ b/002_source/cms/src/app/dashboard/mf/categories/[cat_id]/page.tsx @@ -7,7 +7,7 @@ import SampleAddressCard from '@/app/dashboard/Sample/AddressCard'; import { SampleNotifications } from '@/app/dashboard/Sample/Notifications'; import SamplePaymentCard from '@/app/dashboard/Sample/SamplePaymentCard'; import SampleSecurityCard from '@/app/dashboard/Sample/SampleSecurityCard'; -import { COL_QUIZ_LP_CATEGORIES } from '@/constants'; +import { COL_QUIZ_MF_CATEGORIES } from '@/constants'; import Box from '@mui/material/Box'; import Link from '@mui/material/Link'; import Stack from '@mui/material/Stack'; @@ -21,9 +21,9 @@ import { logger } from '@/lib/default-logger'; import { pb } from '@/lib/pb'; import { toast } from '@/components/core/toaster'; import ErrorDisplay from '@/components/dashboard/error'; -import { defaultLpCategory } from '@/components/dashboard/lp/categories/_constants.ts'; -import { Notifications } from '@/components/dashboard/lp/categories/notifications'; -import type { LpCategory } from '@/components/dashboard/lp/categories/type'; +import { defaultMfCategory } from '@/components/dashboard/mf/categories/_constants.ts'; +import { Notifications } from '@/components/dashboard/mf/categories/notifications'; +import type { MfCategory } from '@/components/dashboard/mf/categories/type'; import FormLoading from '@/components/loading'; import BasicDetailCard from './BasicDetailCard'; @@ -39,18 +39,18 @@ export default function Page(): React.JSX.Element { const [showError, setShowError] = React.useState({ show: false, detail: '' }); // - const [showLessonCategory, setShowLessonCategory] = React.useState(defaultLpCategory); + const [showLessonCategory, setShowLessonCategory] = React.useState(defaultMfCategory); function handleEditClick() { - router.push(paths.dashboard.lp_categories.edit(showLessonCategory.id)); + router.push(paths.dashboard.mf_categories.edit(showLessonCategory.id)); } React.useEffect(() => { if (catId) { - pb.collection(COL_QUIZ_LP_CATEGORIES) + pb.collection(COL_QUIZ_MF_CATEGORIES) .getOne(catId) .then((model: RecordModel) => { - setShowLessonCategory({ ...defaultLpCategory, ...model }); + setShowLessonCategory({ ...defaultMfCategory, ...model }); }) .catch((err) => { logger.error(err); @@ -89,7 +89,7 @@ export default function Page(): React.JSX.Element { diff --git a/002_source/cms/src/app/dashboard/mf/categories/create/page.tsx b/002_source/cms/src/app/dashboard/mf/categories/create/page.tsx index 4bc1776..c0e2539 100644 --- a/002_source/cms/src/app/dashboard/mf/categories/create/page.tsx +++ b/002_source/cms/src/app/dashboard/mf/categories/create/page.tsx @@ -13,7 +13,7 @@ import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/Arrow import { useTranslation } from 'react-i18next'; import { paths } from '@/paths'; -import { LpCategoryCreateForm } from '@/components/dashboard/lp/categories/lp-category-create-form'; +import { MfCategoryCreateForm } from '@/components/dashboard/mf/categories/lp-category-create-form'; export default function Page(): React.JSX.Element { // RULES: follow the name of page directory @@ -34,7 +34,7 @@ export default function Page(): React.JSX.Element { @@ -46,7 +46,7 @@ export default function Page(): React.JSX.Element { {t('create.title')} - + ); diff --git a/002_source/cms/src/app/dashboard/mf/categories/edit/[cat_id]/page.tsx b/002_source/cms/src/app/dashboard/mf/categories/edit/[cat_id]/page.tsx index 149a75b..ab22f31 100644 --- a/002_source/cms/src/app/dashboard/mf/categories/edit/[cat_id]/page.tsx +++ b/002_source/cms/src/app/dashboard/mf/categories/edit/[cat_id]/page.tsx @@ -10,7 +10,7 @@ import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/Arrow import { useTranslation } from 'react-i18next'; import { paths } from '@/paths'; -import { LpCategoryEditForm } from '@/components/dashboard/lp/categories/lp-category-edit-form'; +import { MfCategoryEditForm } from '@/components/dashboard/mf/categories/mf-category-edit-form'; export default function Page(): React.JSX.Element { const { t } = useTranslation(['lp_categories']); @@ -34,7 +34,7 @@ export default function Page(): React.JSX.Element { @@ -46,7 +46,7 @@ export default function Page(): React.JSX.Element { {t('edit.title')} - + ); diff --git a/002_source/cms/src/app/dashboard/mf/categories/page.tsx b/002_source/cms/src/app/dashboard/mf/categories/page.tsx index 9a81a6e..da233d0 100644 --- a/002_source/cms/src/app/dashboard/mf/categories/page.tsx +++ b/002_source/cms/src/app/dashboard/mf/categories/page.tsx @@ -6,7 +6,7 @@ // import * as React from 'react'; import { useRouter } from 'next/navigation'; -import { COL_QUIZ_LP_CATEGORIES } from '@/constants'; +import { COL_QUIZ_MF_CATEGORIES } from '@/constants'; import { LoadingButton } from '@mui/lab'; import Box from '@mui/material/Box'; import Card from '@mui/material/Card'; @@ -22,20 +22,21 @@ import { logger } from '@/lib/default-logger'; import { pb } from '@/lib/pb'; import { toast } from '@/components/core/toaster'; import ErrorDisplay from '@/components/dashboard/error'; -import { defaultLpCategory } from '@/components/dashboard/lp/categories/_constants'; -import { LpCategoriesFilters } from '@/components/dashboard/lp/categories/lp-categories-filters'; -import type { Filters } from '@/components/dashboard/lp/categories/lp-categories-filters'; -import { LpCategoriesPagination } from '@/components/dashboard/lp/categories/lp-categories-pagination'; -import { LpCategoriesSelectionProvider } from '@/components/dashboard/lp/categories/lp-categories-selection-context'; -import { LpCategoriesTable } from '@/components/dashboard/lp/categories/lp-categories-table'; -import type { LpCategory } from '@/components/dashboard/lp/categories/type'; +import { defaultMfCategory } from '@/components/dashboard/mf/categories/_constants'; +import { MfCategoriesFilters } from '@/components/dashboard/mf/categories/mf-categories-filters'; +import type { Filters } from '@/components/dashboard/mf/categories/mf-categories-filters'; +import { MfCategoriesPagination } from '@/components/dashboard/mf/categories/mf-categories-pagination'; +import { MfCategoriesSelectionProvider } from '@/components/dashboard/mf/categories/mf-categories-selection-context'; +import { MfCategoriesTable } from '@/components/dashboard/mf/categories/mf-categories-table'; +import type { MfCategory } from '@/components/dashboard/mf/categories/type'; import FormLoading from '@/components/loading'; export default function Page({ searchParams }: PageProps): React.JSX.Element { + // TODO: modify from lp_categories to mf_categories const { t } = useTranslation(['lp_categories']); const { email, phone, sortDir, status, name, visible, type } = searchParams; const router = useRouter(); - const [lessonCategoriesData, setLessonCategoriesData] = React.useState([]); + const [lessonCategoriesData, setLessonCategoriesData] = React.useState([]); // const [isLoadingAddPage, setIsLoadingAddPage] = React.useState(false); @@ -43,7 +44,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { const [showError, setShowError] = React.useState({ show: false, detail: '' }); // const [rowsPerPage, setRowsPerPage] = React.useState(5); - const [f, setF] = React.useState([]); + const [f, setF] = React.useState([]); const [currentPage, setCurrentPage] = React.useState(1); const [recordCount, setRecordCount] = React.useState(0); const [listOption, setListOption] = React.useState({}); @@ -56,11 +57,11 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { const reloadRows = async (): Promise => { try { const models: ListResult = await pb - .collection(COL_QUIZ_LP_CATEGORIES) + .collection(COL_QUIZ_MF_CATEGORIES) .getList(currentPage + 1, rowsPerPage, {}); const { items, totalItems } = models; - const tempLessonTypes: LpCategory[] = items.map((lt) => { - return { ...defaultLpCategory, ...lt }; + const tempLessonTypes: MfCategory[] = items.map((lt) => { + return { ...defaultMfCategory, ...lt }; }); setLessonCategoriesData(tempLessonTypes); @@ -113,7 +114,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { loading={isLoadingAddPage} onClick={(): void => { setIsLoadingAddPage(true); - router.push(paths.dashboard.lp_categories.create); + router.push(paths.dashboard.mf_categories.create); }} startIcon={} variant="contained" @@ -122,22 +123,22 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { - + - - - - + ); @@ -153,7 +154,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { // Sorting and filtering has to be done on the server. -function applySort(row: LpCategory[], sortDir: 'asc' | 'desc' | undefined): LpCategory[] { +function applySort(row: MfCategory[], sortDir: 'asc' | 'desc' | undefined): MfCategory[] { return row.sort((a, b) => { if (sortDir === 'asc') { return a.createdAt.getTime() - b.createdAt.getTime(); @@ -163,7 +164,7 @@ function applySort(row: LpCategory[], sortDir: 'asc' | 'desc' | undefined): LpCa }); } -function applyFilters(row: LpCategory[], { email, phone, status, name, visible }: Filters): LpCategory[] { +function applyFilters(row: MfCategory[], { email, phone, status, name, visible }: Filters): MfCategory[] { return row.filter((item) => { if (email) { if (!item.email?.toLowerCase().includes(email.toLowerCase())) { diff --git a/002_source/cms/src/app/dashboard/mf/questions/[cat_id]/BasicDetailCard.tsx b/002_source/cms/src/app/dashboard/mf/questions/[cat_id]/BasicDetailCard.tsx index 6683056..9c013b8 100644 --- a/002_source/cms/src/app/dashboard/mf/questions/[cat_id]/BasicDetailCard.tsx +++ b/002_source/cms/src/app/dashboard/mf/questions/[cat_id]/BasicDetailCard.tsx @@ -13,13 +13,13 @@ import { useTranslation } from 'react-i18next'; import { PropertyItem } from '@/components/core/property-item'; import { PropertyList } from '@/components/core/property-list'; -import { LpCategory } from '@/components/dashboard/lp/categories/type'; +import { MfCategory } from '@/components/dashboard/mf/categories/type'; export default function BasicDetailCard({ lpModel: model, handleEditClick, }: { - lpModel: LpCategory; + lpModel: MfCategory; handleEditClick: () => void; }): React.JSX.Element { const { t } = useTranslation(); diff --git a/002_source/cms/src/app/dashboard/mf/questions/[cat_id]/TitleCard.tsx b/002_source/cms/src/app/dashboard/mf/questions/[cat_id]/TitleCard.tsx index 6e38fb6..059baaf 100644 --- a/002_source/cms/src/app/dashboard/mf/questions/[cat_id]/TitleCard.tsx +++ b/002_source/cms/src/app/dashboard/mf/questions/[cat_id]/TitleCard.tsx @@ -10,13 +10,13 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { useTranslation } from 'react-i18next'; -import { LpCategory } from '@/components/dashboard/lp/categories/type'; +import type { MfCategory } from '@/components/dashboard/mf/categories/type'; -function getImageUrlFrRecord(record: LpCategory): string { +function getImageUrlFrRecord(record: MfCategory): string { return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`; } -export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element { +export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element { const { t } = useTranslation(); return ( diff --git a/002_source/cms/src/app/dashboard/mf/questions/[cat_id]/page.tsx b/002_source/cms/src/app/dashboard/mf/questions/[cat_id]/page.tsx index dacc408..135e49d 100644 --- a/002_source/cms/src/app/dashboard/mf/questions/[cat_id]/page.tsx +++ b/002_source/cms/src/app/dashboard/mf/questions/[cat_id]/page.tsx @@ -7,7 +7,7 @@ import SampleAddressCard from '@/app/dashboard/Sample/AddressCard'; import { SampleNotifications } from '@/app/dashboard/Sample/Notifications'; import SamplePaymentCard from '@/app/dashboard/Sample/SamplePaymentCard'; import SampleSecurityCard from '@/app/dashboard/Sample/SampleSecurityCard'; -import { COL_QUIZ_LP_QUESTIONS } from '@/constants'; +import { COL_QUIZ_MF_QUESTIONS } from '@/constants'; import { Grid } from '@mui/material'; import Box from '@mui/material/Box'; import Link from '@mui/material/Link'; @@ -21,9 +21,9 @@ import { logger } from '@/lib/default-logger'; import { pb } from '@/lib/pb'; import { toast } from '@/components/core/toaster'; import ErrorDisplay from '@/components/dashboard/error'; -import { defaultLpQuestion } from '@/components/dashboard/lp/questions/_constants.ts'; -import { Notifications } from '@/components/dashboard/lp/questions/notifications'; -import type { LpQuestion } from '@/components/dashboard/lp/questions/type'; +import { defaultMfQuestion } from '@/components/dashboard/mf/questions/_constants.ts'; +import { Notifications } from '@/components/dashboard/mf/questions/notifications'; +import type { LpQuestion } from '@/components/dashboard/mf/questions/type'; import FormLoading from '@/components/loading'; import BasicDetailCard from './BasicDetailCard'; @@ -39,7 +39,7 @@ export default function Page(): React.JSX.Element { const [showError, setShowError] = React.useState({ show: false, detail: '' }); // - const [showLessonQuestion, setShowLessonQuestion] = React.useState(defaultLpQuestion); + const [showLessonQuestion, setShowLessonQuestion] = React.useState(defaultMfQuestion); function handleEditClick() { router.push(paths.dashboard.lp_questions.edit(showLessonQuestion.id)); @@ -47,10 +47,10 @@ export default function Page(): React.JSX.Element { React.useEffect(() => { if (catId) { - pb.collection(COL_QUIZ_LP_QUESTIONS) + pb.collection(COL_QUIZ_MF_QUESTIONS) .getOne(catId) .then((model: RecordModel) => { - setShowLessonQuestion({ ...defaultLpQuestion, ...model }); + setShowLessonQuestion({ ...defaultMfQuestion, ...model }); }) .catch((err) => { logger.error(err); diff --git a/002_source/cms/src/app/dashboard/mf/questions/create/page.tsx b/002_source/cms/src/app/dashboard/mf/questions/create/page.tsx index ff3c9dc..a63af0e 100644 --- a/002_source/cms/src/app/dashboard/mf/questions/create/page.tsx +++ b/002_source/cms/src/app/dashboard/mf/questions/create/page.tsx @@ -13,7 +13,7 @@ import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/Arrow import { useTranslation } from 'react-i18next'; import { paths } from '@/paths'; -import { LpQuestionCreateForm } from '@/components/dashboard/lp/questions/lp-question-create-form'; +import { MfQuestionCreateForm } from '@/components/dashboard/mf/questions/mf-question-create-form'; export default function Page(): React.JSX.Element { // RULES: follow the name of page directory @@ -46,7 +46,7 @@ export default function Page(): React.JSX.Element { {t('create.title')} - + ); diff --git a/002_source/cms/src/app/dashboard/mf/questions/edit/[cat_id]/page.tsx b/002_source/cms/src/app/dashboard/mf/questions/edit/[cat_id]/page.tsx index 321b5a9..0f01dad 100644 --- a/002_source/cms/src/app/dashboard/mf/questions/edit/[cat_id]/page.tsx +++ b/002_source/cms/src/app/dashboard/mf/questions/edit/[cat_id]/page.tsx @@ -10,7 +10,7 @@ import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/Arrow import { useTranslation } from 'react-i18next'; import { paths } from '@/paths'; -import { LpQuestionEditForm } from '@/components/dashboard/lp/questions/lp-question-edit-form'; +import { MfQuestionEditForm } from '@/components/dashboard/mf/questions/mf-question-edit-form'; export default function Page(): React.JSX.Element { const { t } = useTranslation(['lp_questions']); @@ -46,7 +46,7 @@ export default function Page(): React.JSX.Element { {t('edit.title')} - + ); diff --git a/002_source/cms/src/app/dashboard/mf/questions/page.tsx b/002_source/cms/src/app/dashboard/mf/questions/page.tsx index 31990a3..eecb6a0 100644 --- a/002_source/cms/src/app/dashboard/mf/questions/page.tsx +++ b/002_source/cms/src/app/dashboard/mf/questions/page.tsx @@ -6,7 +6,7 @@ // import * as React from 'react'; import { useRouter } from 'next/navigation'; -import { COL_QUIZ_LP_QUESTIONS } from '@/constants'; +import { COL_QUIZ_MF_QUESTIONS } from '@/constants'; import { LoadingButton } from '@mui/lab'; import Box from '@mui/material/Box'; import Card from '@mui/material/Card'; @@ -22,20 +22,20 @@ import { logger } from '@/lib/default-logger'; import { pb } from '@/lib/pb'; import { toast } from '@/components/core/toaster'; import ErrorDisplay from '@/components/dashboard/error'; -import { defaultLpQuestion } from '@/components/dashboard/lp/questions/_constants'; -import { LpQuestionsFilters } from '@/components/dashboard/lp/questions/lp-questions-filters'; -import type { Filters } from '@/components/dashboard/lp/questions/lp-questions-filters'; -import { LpQuestionsPagination } from '@/components/dashboard/lp/questions/lp-questions-pagination'; -import { LpQuestionsSelectionProvider } from '@/components/dashboard/lp/questions/lp-questions-selection-context'; -import { LpQuestionsTable } from '@/components/dashboard/lp/questions/lp-questions-table'; -import type { LpQuestion } from '@/components/dashboard/lp/questions/type'; +import { defaultMfQuestion } from '@/components/dashboard/mf/questions/_constants'; +import { MfQuestionsFilters } from '@/components/dashboard/mf/questions/mf-questions-filters'; +import type { Filters } from '@/components/dashboard/mf/questions/mf-questions-filters'; +import { MfQuestionsPagination } from '@/components/dashboard/mf/questions/mf-questions-pagination'; +import { MfQuestionsSelectionProvider } from '@/components/dashboard/mf/questions/mf-questions-selection-context'; +import { MfQuestionsTable } from '@/components/dashboard/mf/questions/mf-questions-table'; +import type { MfQuestion } from '@/components/dashboard/mf/questions/type'; import FormLoading from '@/components/loading'; export default function Page({ searchParams }: PageProps): React.JSX.Element { const { t } = useTranslation(['lp_questions']); const { email, phone, sortDir, status, name, visible, type } = searchParams; const router = useRouter(); - const [lessonQuestionsData, setLessonCategoriesData] = React.useState([]); + const [lessonQuestionsData, setLessonCategoriesData] = React.useState([]); // const [isLoadingAddPage, setIsLoadingAddPage] = React.useState(false); @@ -43,7 +43,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { const [showError, setShowError] = React.useState({ show: false, detail: '' }); // const [rowsPerPage, setRowsPerPage] = React.useState(5); - const [f, setF] = React.useState([]); + const [f, setF] = React.useState([]); const [currentPage, setCurrentPage] = React.useState(0); const [recordCount, setRecordCount] = React.useState(0); const [listOption, setListOption] = React.useState({}); @@ -56,11 +56,11 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { const reloadRows = async (): Promise => { try { const models: ListResult = await pb - .collection(COL_QUIZ_LP_QUESTIONS) + .collection(COL_QUIZ_MF_QUESTIONS) .getList(currentPage + 1, rowsPerPage, listOption); const { items, totalItems } = models; - const tempLessonTypes: LpQuestion[] = items.map((lt) => { - return { ...defaultLpQuestion, ...lt }; + const tempLessonTypes: MfQuestion[] = items.map((lt) => { + return { ...defaultMfQuestion, ...lt }; }); setLessonCategoriesData(tempLessonTypes); @@ -177,22 +177,22 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { - + - - - - +
{JSON.stringify(f, null, 2)}
@@ -209,7 +209,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { // Sorting and filtering has to be done on the server. -function applySort(row: LpQuestion[], sortDir: 'asc' | 'desc' | undefined): LpQuestion[] { +function applySort(row: MfQuestion[], sortDir: 'asc' | 'desc' | undefined): MfQuestion[] { return row.sort((a, b) => { if (sortDir === 'asc') { return a.createdAt.getTime() - b.createdAt.getTime(); @@ -219,7 +219,7 @@ function applySort(row: LpQuestion[], sortDir: 'asc' | 'desc' | undefined): LpQu }); } -function applyFilters(row: LpQuestion[], { email, phone, status, name, visible }: Filters): LpQuestion[] { +function applyFilters(row: MfQuestion[], { email, phone, status, name, visible }: Filters): MfQuestion[] { return row.filter((item) => { if (email) { if (!item.email?.toLowerCase().includes(email.toLowerCase())) { diff --git a/002_source/cms/src/components/dashboard/mf/categories/lp-categories-filters.tsx b/002_source/cms/src/components/dashboard/lp/categories/mf-categories-filters.tsx similarity index 100% rename from 002_source/cms/src/components/dashboard/mf/categories/lp-categories-filters.tsx rename to 002_source/cms/src/components/dashboard/lp/categories/mf-categories-filters.tsx diff --git a/002_source/cms/src/components/dashboard/mf/categories/lp-categories-pagination.tsx b/002_source/cms/src/components/dashboard/lp/categories/mf-categories-pagination.tsx similarity index 100% rename from 002_source/cms/src/components/dashboard/mf/categories/lp-categories-pagination.tsx rename to 002_source/cms/src/components/dashboard/lp/categories/mf-categories-pagination.tsx diff --git a/002_source/cms/src/components/dashboard/mf/categories/lp-categories-selection-context.tsx b/002_source/cms/src/components/dashboard/lp/categories/mf-categories-selection-context.tsx similarity index 100% rename from 002_source/cms/src/components/dashboard/mf/categories/lp-categories-selection-context.tsx rename to 002_source/cms/src/components/dashboard/lp/categories/mf-categories-selection-context.tsx diff --git a/002_source/cms/src/components/dashboard/mf/categories/lp-categories-table.tsx b/002_source/cms/src/components/dashboard/lp/categories/mf-categories-table.tsx similarity index 100% rename from 002_source/cms/src/components/dashboard/mf/categories/lp-categories-table.tsx rename to 002_source/cms/src/components/dashboard/lp/categories/mf-categories-table.tsx diff --git a/002_source/cms/src/components/dashboard/mf/questions/lp-question-create-form.tsx b/002_source/cms/src/components/dashboard/lp/categories/mf-category-create-form.tsx similarity index 99% rename from 002_source/cms/src/components/dashboard/mf/questions/lp-question-create-form.tsx rename to 002_source/cms/src/components/dashboard/lp/categories/mf-category-create-form.tsx index a39ad69..ba46ac7 100644 --- a/002_source/cms/src/components/dashboard/mf/questions/lp-question-create-form.tsx +++ b/002_source/cms/src/components/dashboard/lp/categories/mf-category-create-form.tsx @@ -66,7 +66,7 @@ export const defaultValues = { description: '', } satisfies Values; -export function LpQuestionCreateForm(): React.JSX.Element { +export function LpCategoryCreateForm(): React.JSX.Element { const router = useRouter(); const { t } = useTranslation(['lp_categories']); diff --git a/002_source/cms/src/components/dashboard/mf/categories/lp-category-edit-form.tsx b/002_source/cms/src/components/dashboard/lp/categories/mf-category-edit-form.tsx similarity index 100% rename from 002_source/cms/src/components/dashboard/mf/categories/lp-category-edit-form.tsx rename to 002_source/cms/src/components/dashboard/lp/categories/mf-category-edit-form.tsx diff --git a/002_source/cms/src/components/dashboard/mf/categories/_constants.ts b/002_source/cms/src/components/dashboard/mf/categories/_constants.ts index 885067e..23fb774 100644 --- a/002_source/cms/src/components/dashboard/mf/categories/_constants.ts +++ b/002_source/cms/src/components/dashboard/mf/categories/_constants.ts @@ -1,8 +1,8 @@ import { dayjs } from '@/lib/dayjs'; -import { CreateFormProps, LpCategory } from './type'; +import { CreateFormProps, MfCategory } from './type'; -export const defaultLpCategory: LpCategory = { +export const defaultMfCategory: MfCategory = { isEmpty: false, id: 'default-id', cat_name: 'default-category-name', @@ -38,7 +38,7 @@ export const defaultLpCategory: LpCategory = { // imageUrl: '', // }; -export const emptyLpCategory: LpCategory = { - ...defaultLpCategory, +export const emptyLpCategory: MfCategory = { + ...defaultMfCategory, isEmpty: true, }; diff --git a/002_source/cms/src/components/dashboard/mf/categories/lp-category-create-form.tsx b/002_source/cms/src/components/dashboard/mf/categories/lp-category-create-form.tsx index ba46ac7..5f341b8 100644 --- a/002_source/cms/src/components/dashboard/mf/categories/lp-category-create-form.tsx +++ b/002_source/cms/src/components/dashboard/mf/categories/lp-category-create-form.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import RouterLink from 'next/link'; import { useRouter } from 'next/navigation'; -import { COL_QUIZ_LP_CATEGORIES } from '@/constants'; +import { COL_QUIZ_MF_CATEGORIES } from '@/constants'; import { zodResolver } from '@hookform/resolvers/zod'; import { LoadingButton } from '@mui/lab'; import { Avatar, Divider, MenuItem, Select } from '@mui/material'; @@ -66,7 +66,7 @@ export const defaultValues = { description: '', } satisfies Values; -export function LpCategoryCreateForm(): React.JSX.Element { +export function MfCategoryCreateForm(): React.JSX.Element { const router = useRouter(); const { t } = useTranslation(['lp_categories']); @@ -101,11 +101,11 @@ export function LpCategoryCreateForm(): React.JSX.Element { }; try { - const result = await pb.collection(COL_QUIZ_LP_CATEGORIES).create(payload); + const result = await pb.collection(COL_QUIZ_MF_CATEGORIES).create(payload); logger.debug(result); toast.success(t('create.success')); - router.push(paths.dashboard.lp_categories.list); + router.push(paths.dashboard.mf_categories.list); } catch (error) { logger.error(error); toast.error(t('create.failed')); diff --git a/002_source/cms/src/components/dashboard/mf/categories/mf-categories-filters.tsx b/002_source/cms/src/components/dashboard/mf/categories/mf-categories-filters.tsx new file mode 100644 index 0000000..d12e9e1 --- /dev/null +++ b/002_source/cms/src/components/dashboard/mf/categories/mf-categories-filters.tsx @@ -0,0 +1,456 @@ +'use client'; + +import * as React from 'react'; +import { useRouter } from 'next/navigation'; +// import { COL_LESSON_CATEGORIES } from '@/constants'; +import GetAllCount from '@/db/QuizListenings/GetAllCount'; +import GetHiddenCount from '@/db/QuizListenings/GetHiddenCount'; +import GetVisibleCount from '@/db/QuizListenings/GetVisibleCount'; +import Button from '@mui/material/Button'; +import Chip from '@mui/material/Chip'; +import Divider from '@mui/material/Divider'; +import FormControl from '@mui/material/FormControl'; +import OutlinedInput from '@mui/material/OutlinedInput'; +import Select from '@mui/material/Select'; +import type { SelectChangeEvent } from '@mui/material/Select'; +import Stack from '@mui/material/Stack'; +import Tab from '@mui/material/Tab'; +import Tabs from '@mui/material/Tabs'; +import Typography from '@mui/material/Typography'; +import { useTranslation } from 'react-i18next'; + +import { paths } from '@/paths'; +import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button'; +import { Option } from '@/components/core/option'; + +import { useLpCategoriesSelection } from './mf-categories-selection-context'; +import { MfCategory } from './type'; + +export interface Filters { + email?: string; + phone?: string; + status?: string; + name?: string; + visible?: string; + type?: string; +} + +export type SortDir = 'asc' | 'desc'; + +export interface LpCategoriesFiltersProps { + filters?: Filters; + sortDir?: SortDir; + fullData: MfCategory[]; +} + +export function MfCategoriesFilters({ + filters = {}, + sortDir = 'desc', + fullData, +}: LpCategoriesFiltersProps): React.JSX.Element { + const { t } = useTranslation(); + const { email, phone, status, name, visible, type } = filters; + + const [totalCount, setTotalCount] = React.useState(0); + const [visibleCount, setVisibleCount] = React.useState(0); + const [hiddenCount, setHiddenCount] = React.useState(0); + + const router = useRouter(); + + const selection = useLpCategoriesSelection(); + + function getVisible(): number { + return fullData.reduce((count, item: MfCategory) => { + return item.visible === 'visible' ? count + 1 : count; + }, 0); + } + + function getHidden(): number { + return fullData.reduce((count, item: MfCategory) => { + return item.visible === 'hidden' ? count + 1 : count; + }, 0); + } + + // The tabs should be generated using API data. + const tabs = [ + { label: t('All'), value: '', count: totalCount }, + // { label: 'Active', value: 'active', count: 3 }, + // { label: 'Pending', value: 'pending', count: 1 }, + // { label: 'Blocked', value: 'blocked', count: 1 }, + { label: t('visible'), value: 'visible', count: visibleCount }, + { label: t('hidden'), value: 'hidden', count: hiddenCount }, + ] as const; + + const updateSearchParams = React.useCallback( + (newFilters: Filters, newSortDir: SortDir): void => { + const searchParams = new URLSearchParams(); + + if (newSortDir === 'asc') { + searchParams.set('sortDir', newSortDir); + } + + if (newFilters.status) { + searchParams.set('status', newFilters.status); + } + + if (newFilters.email) { + searchParams.set('email', newFilters.email); + } + + if (newFilters.phone) { + searchParams.set('phone', newFilters.phone); + } + + if (newFilters.name) { + searchParams.set('name', newFilters.name); + } + + if (newFilters.type) { + searchParams.set('type', newFilters.type); + } + + if (newFilters.visible) { + searchParams.set('visible', newFilters.visible); + } + + // NOTE: modify according to COLLECTION + router.push(`${paths.dashboard.mf_categories.list}?${searchParams.toString()}`); + }, + [router] + ); + + const handleClearFilters = React.useCallback(() => { + updateSearchParams({}, sortDir); + }, [updateSearchParams, sortDir]); + + const handleStatusChange = React.useCallback( + (_: React.SyntheticEvent, value: string) => { + updateSearchParams({ ...filters, status: value }, sortDir); + }, + [updateSearchParams, filters, sortDir] + ); + + const handleVisibleChange = React.useCallback( + (_: React.SyntheticEvent, value: string) => { + updateSearchParams({ ...filters, visible: value }, sortDir); + }, + [updateSearchParams, filters, sortDir] + ); + + const handleNameChange = React.useCallback( + (value?: string) => { + updateSearchParams({ ...filters, name: value }, sortDir); + }, + [updateSearchParams, filters, sortDir] + ); + + const handleTypeChange = React.useCallback( + (value?: string) => { + updateSearchParams({ ...filters, type: value }, sortDir); + }, + [updateSearchParams, filters, sortDir] + ); + + const handleEmailChange = React.useCallback( + (value?: string) => { + updateSearchParams({ ...filters, email: value }, sortDir); + }, + [updateSearchParams, filters, sortDir] + ); + + const handlePhoneChange = React.useCallback( + (value?: string) => { + updateSearchParams({ ...filters, phone: value }, sortDir); + }, + [updateSearchParams, filters, sortDir] + ); + + const handleSortChange = React.useCallback( + (event: SelectChangeEvent) => { + updateSearchParams(filters, event.target.value as SortDir); + }, + [updateSearchParams, filters] + ); + + React.useEffect(() => { + const fetchCount = async (): Promise => { + try { + const tc = await GetAllCount(); + setTotalCount(tc); + + const vc = await GetVisibleCount(); + setVisibleCount(vc); + + const hc = await GetHiddenCount(); + setHiddenCount(hc); + } catch (error) { + // + } + }; + void fetchCount(); + }, []); + + const hasFilters = status || email || phone || visible || name || type; + + return ( +
+ + {tabs.map((tab) => ( + + } + iconPosition="end" + key={tab.value} + label={tab.label} + sx={{ minHeight: 'auto' }} + tabIndex={0} + value={tab.value} + /> + ))} + + + + + { + handleNameChange(value as string); + }} + onFilterDelete={() => { + handleNameChange(); + }} + popover={} + value={name} + /> + + { + handleTypeChange(value as string); + }} + onFilterDelete={() => { + handleTypeChange(); + }} + popover={} + value={type} + /> + + {hasFilters ? : null} + + {selection.selectedAny ? ( + + + {selection.selected.size} {t('selected')} + + + + ) : null} + + +
+ ); +} + +function TypeFilterPopover(): React.JSX.Element { + const { t } = useTranslation(); + const { anchorEl, onApply, onClose, open, value: initialValue } = useFilterContext(); + const [value, setValue] = React.useState(''); + + React.useEffect(() => { + setValue((initialValue as string | undefined) ?? ''); + }, [initialValue]); + + return ( + + + { + setValue(event.target.value); + }} + onKeyUp={(event) => { + if (event.key === 'Enter') { + onApply(value); + } + }} + value={value} + /> + + + + ); +} + +function NameFilterPopover(): React.JSX.Element { + const { t } = useTranslation(); + const { anchorEl, onApply, onClose, open, value: initialValue } = useFilterContext(); + const [value, setValue] = React.useState(''); + + React.useEffect(() => { + setValue((initialValue as string | undefined) ?? ''); + }, [initialValue]); + + return ( + + + { + setValue(event.target.value); + }} + onKeyUp={(event) => { + if (event.key === 'Enter') { + onApply(value); + } + }} + value={value} + /> + + + + ); +} + +function EmailFilterPopover(): React.JSX.Element { + const { anchorEl, onApply, onClose, open, value: initialValue } = useFilterContext(); + const [value, setValue] = React.useState(''); + const { t } = useTranslation(); + + React.useEffect(() => { + setValue((initialValue as string | undefined) ?? ''); + }, [initialValue]); + + return ( + + + { + setValue(event.target.value); + }} + onKeyUp={(event) => { + if (event.key === 'Enter') { + onApply(value); + } + }} + value={value} + /> + + + + ); +} + +function PhoneFilterPopover(): React.JSX.Element { + const { anchorEl, onApply, onClose, open, value: initialValue } = useFilterContext(); + const [value, setValue] = React.useState(''); + const { t } = useTranslation(); + + React.useEffect(() => { + setValue((initialValue as string | undefined) ?? ''); + }, [initialValue]); + + return ( + + + { + setValue(event.target.value); + }} + onKeyUp={(event) => { + if (event.key === 'Enter') { + onApply(value); + } + }} + value={value} + /> + + + + ); +} diff --git a/002_source/cms/src/components/dashboard/mf/categories/mf-categories-pagination.tsx b/002_source/cms/src/components/dashboard/mf/categories/mf-categories-pagination.tsx new file mode 100644 index 0000000..76efa5a --- /dev/null +++ b/002_source/cms/src/components/dashboard/mf/categories/mf-categories-pagination.tsx @@ -0,0 +1,49 @@ +'use client'; + +import * as React from 'react'; +import TablePagination from '@mui/material/TablePagination'; + +function noop(): void { + return undefined; +} + +interface LessonCategoriesPaginationProps { + count: number; + page: number; + // + setPage: (page: number) => void; + setRowsPerPage: (page: number) => void; + rowsPerPage: number; +} + +export function MfCategoriesPagination({ + count, + page, + // + setPage, + setRowsPerPage, + rowsPerPage, +}: LessonCategoriesPaginationProps): React.JSX.Element { + // You should implement the pagination using a similar logic as the filters. + // Note that when page change, you should keep the filter search params. + const handleChangePage = (event: unknown, newPage: number) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event: React.ChangeEvent) => { + setRowsPerPage(parseInt(event.target.value)); + // console.log(parseInt(event.target.value)); + }; + + return ( + + ); +} diff --git a/002_source/cms/src/components/dashboard/mf/categories/mf-categories-selection-context.tsx b/002_source/cms/src/components/dashboard/mf/categories/mf-categories-selection-context.tsx new file mode 100644 index 0000000..8dbd7c2 --- /dev/null +++ b/002_source/cms/src/components/dashboard/mf/categories/mf-categories-selection-context.tsx @@ -0,0 +1,46 @@ +'use client'; + +import * as React from 'react'; + +// import type { LessonCategory } from '@/types/lesson-type'; +import { useSelection } from '@/hooks/use-selection'; +import type { Selection } from '@/hooks/use-selection'; + +import { MfCategory } from './type'; + +function noop(): void { + return undefined; +} + +export interface LpCategoriesSelectionContextValue extends Selection {} + +export const LpCategoriesSelectionContext = React.createContext({ + deselectAll: noop, + deselectOne: noop, + selectAll: noop, + selectOne: noop, + selected: new Set(), + selectedAny: false, + selectedAll: false, +}); + +interface LpCategoriesSelectionProviderProps { + children: React.ReactNode; + lessonCategories: MfCategory[]; +} + +export function MfCategoriesSelectionProvider({ + children, + lessonCategories = [], +}: LpCategoriesSelectionProviderProps): React.JSX.Element { + const customerIds = React.useMemo(() => lessonCategories.map((customer) => customer.id), [lessonCategories]); + const selection = useSelection(customerIds); + + return ( + {children} + ); +} + +export function useLpCategoriesSelection(): LpCategoriesSelectionContextValue { + return React.useContext(LpCategoriesSelectionContext); +} diff --git a/002_source/cms/src/components/dashboard/mf/categories/mf-categories-table.tsx b/002_source/cms/src/components/dashboard/mf/categories/mf-categories-table.tsx new file mode 100644 index 0000000..e67175d --- /dev/null +++ b/002_source/cms/src/components/dashboard/mf/categories/mf-categories-table.tsx @@ -0,0 +1,241 @@ +'use client'; + +import * as React from 'react'; +import RouterLink from 'next/link'; +import { LoadingButton } from '@mui/lab'; +import Avatar from '@mui/material/Avatar'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import LinearProgress from '@mui/material/LinearProgress'; +import Link from '@mui/material/Link'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; +import { Clock as ClockIcon } from '@phosphor-icons/react/dist/ssr/Clock'; +import { Images as ImagesIcon } from '@phosphor-icons/react/dist/ssr/Images'; +import { Minus as MinusIcon } from '@phosphor-icons/react/dist/ssr/Minus'; +import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple'; +import { TrashSimple as TrashSimpleIcon } from '@phosphor-icons/react/dist/ssr/TrashSimple'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; + +import { paths } from '@/paths'; +import { dayjs } from '@/lib/dayjs'; +import { DataTable } from '@/components/core/data-table'; +import type { ColumnDef } from '@/components/core/data-table'; + +import ConfirmDeleteModal from './confirm-delete-modal'; +import { useLpCategoriesSelection } from './mf-categories-selection-context'; +import type { MfCategory } from './type'; + +function columns(handleDeleteClick: (testId: string) => void): ColumnDef[] { + return [ + { + formatter: (row): React.JSX.Element => ( + + + + + + {' '} +
+ {row.cat_name} + + slug: {row.cat_name} + +
+
+ +
+ ), + name: 'Name', + width: '200px', + }, + { + formatter: (row): React.JSX.Element => ( + + + + {new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 2 }).format(row.quota / 100)} + + + ), + // NOTE: please refer to translation.json here + name: 'word-count', + width: '100px', + }, + { + formatter: (row): React.JSX.Element => { + // eslint-disable-next-line react-hooks/rules-of-hooks + + const mapping = { + active: { + label: 'Active', + icon: ( + + ), + }, + blocked: { label: 'Blocked', icon: }, + pending: { + label: 'Pending', + icon: ( + + ), + }, + NA: { + label: 'NA', + icon: ( + + ), + }, + } as const; + const { label, icon } = mapping[row.status] ?? { label: 'Unknown', icon: null }; + + return ( + + ); + }, + name: 'Status', + width: '150px', + }, + { + formatter(row) { + return dayjs(row.createdAt).format('MMM D, YYYY'); + }, + name: 'Created at', + width: '100px', + }, + { + formatter: (row): React.JSX.Element => ( + + + + + { + handleDeleteClick(row.id); + }} + > + + + + ), + name: 'Actions', + hideName: true, + width: '100px', + align: 'right', + }, + ]; +} + +export interface LessonCategoriesTableProps { + rows: MfCategory[]; + reloadRows: () => void; +} + +export function MfCategoriesTable({ rows, reloadRows }: LessonCategoriesTableProps): React.JSX.Element { + const { t } = useTranslation(['lp_categories']); + const { deselectAll, deselectOne, selectAll, selectOne, selected } = useLpCategoriesSelection(); + + const [idToDelete, setIdToDelete] = React.useState(''); + const [open, setOpen] = React.useState(false); + + function handleDeleteClick(testId: string): void { + setOpen(true); + setIdToDelete(testId); + } + + return ( + + + + columns={columns(handleDeleteClick)} + onDeselectAll={deselectAll} + onDeselectOne={(_, row) => { + deselectOne(row.id); + }} + onSelectAll={selectAll} + onSelectOne={(_, row) => { + selectOne(row.id); + }} + rows={rows} + selectable + selected={selected} + /> + {!rows.length ? ( + + + {t('no-lesson-categories-found')} + + + ) : null} + + ); +} diff --git a/002_source/cms/src/components/dashboard/mf/questions/lp-question-edit-form.tsx b/002_source/cms/src/components/dashboard/mf/categories/mf-category-edit-form.tsx similarity index 97% rename from 002_source/cms/src/components/dashboard/mf/questions/lp-question-edit-form.tsx rename to 002_source/cms/src/components/dashboard/mf/categories/mf-category-edit-form.tsx index a38d9b2..074f8d3 100644 --- a/002_source/cms/src/components/dashboard/mf/questions/lp-question-edit-form.tsx +++ b/002_source/cms/src/components/dashboard/mf/categories/mf-category-edit-form.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import RouterLink from 'next/link'; import { useParams, useRouter } from 'next/navigation'; // -import { COL_QUIZ_LP_CATEGORIES } from '@/constants'; +import { COL_QUIZ_MF_CATEGORIES } from '@/constants'; import { zodResolver } from '@hookform/resolvers/zod'; import { LoadingButton } from '@mui/lab'; // @@ -88,7 +88,7 @@ const defaultValues = { description: '', } satisfies Values; -export function LpQuestionEditForm(): React.JSX.Element { +export function MfCategoryEditForm(): React.JSX.Element { const router = useRouter(); const { t } = useTranslation(['lp_categories']); @@ -129,10 +129,10 @@ export function LpQuestionEditForm(): React.JSX.Element { }; try { - const result = await pb.collection(COL_QUIZ_LP_CATEGORIES).update(catId, tempUpdate); + const result = await pb.collection(COL_QUIZ_MF_CATEGORIES).update(catId, tempUpdate); logger.debug(result); toast.success(t('edit.success')); - router.push(paths.dashboard.lp_categories.list); + router.push(paths.dashboard.mf_categories.list); } catch (error) { logger.error(error); toast.error(t('update.failed')); @@ -171,7 +171,7 @@ export function LpQuestionEditForm(): React.JSX.Element { setShowLoading(true); try { - const result = await pb.collection(COL_QUIZ_LP_CATEGORIES).getOne(id); + const result = await pb.collection(COL_QUIZ_MF_CATEGORIES).getOne(id); reset({ ...defaultValues, ...result, init_answer: JSON.stringify(result.init_answer) }); setTextDescription(result.description); @@ -480,7 +480,7 @@ export function LpQuestionEditForm(): React.JSX.Element { diff --git a/002_source/cms/src/components/dashboard/mf/categories/type.d.ts b/002_source/cms/src/components/dashboard/mf/categories/type.d.ts index c66a11f..fc84350 100644 --- a/002_source/cms/src/components/dashboard/mf/categories/type.d.ts +++ b/002_source/cms/src/components/dashboard/mf/categories/type.d.ts @@ -1,4 +1,4 @@ -export interface LpCategory { +export interface MfCategory { isEmpty?: boolean; // id: string; diff --git a/002_source/cms/src/components/dashboard/mf/questions/_constants.ts b/002_source/cms/src/components/dashboard/mf/questions/_constants.ts index 95a4d17..076328c 100644 --- a/002_source/cms/src/components/dashboard/mf/questions/_constants.ts +++ b/002_source/cms/src/components/dashboard/mf/questions/_constants.ts @@ -1,8 +1,8 @@ import { dayjs } from '@/lib/dayjs'; -import { CreateFormProps, LpQuestion } from './type'; +import { CreateFormProps, MfQuestion } from './type'; -export const defaultLpQuestion: LpQuestion = { +export const defaultMfQuestion: MfQuestion = { isEmpty: false, id: 'default-id', cat_name: 'default-question-name', @@ -38,7 +38,7 @@ export const defaultLpQuestion: LpQuestion = { // imageUrl: '', // }; -export const emptyLpQuestion: LpQuestion = { - ...defaultLpQuestion, +export const emptyLpQuestion: MfQuestion = { + ...defaultMfQuestion, isEmpty: true, }; diff --git a/002_source/cms/src/components/dashboard/mf/questions/mf-question-create-form.tsx b/002_source/cms/src/components/dashboard/mf/questions/mf-question-create-form.tsx new file mode 100644 index 0000000..90ec4c3 --- /dev/null +++ b/002_source/cms/src/components/dashboard/mf/questions/mf-question-create-form.tsx @@ -0,0 +1,419 @@ +'use client'; + +import * as React from 'react'; +import RouterLink from 'next/link'; +import { useRouter } from 'next/navigation'; +import { COL_QUIZ_MF_CATEGORIES } from '@/constants'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { LoadingButton } from '@mui/lab'; +import { Avatar, Divider, MenuItem, Select } from '@mui/material'; +// import Avatar from '@mui/material/Avatar'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Card from '@mui/material/Card'; +import CardActions from '@mui/material/CardActions'; +import CardContent from '@mui/material/CardContent'; +import FormControl from '@mui/material/FormControl'; +import FormHelperText from '@mui/material/FormHelperText'; +import InputLabel from '@mui/material/InputLabel'; +import OutlinedInput from '@mui/material/OutlinedInput'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import Grid from '@mui/material/Unstable_Grid2'; +import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera'; +// import axios from 'axios'; +import { Controller, useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { z as zod } from 'zod'; + +import { paths } from '@/paths'; +import isDevelopment from '@/lib/check-is-development'; +import { logger } from '@/lib/default-logger'; +import { base64ToFile, fileToBase64 } from '@/lib/file-to-base64'; +import { pb } from '@/lib/pb'; +import { TextEditor } from '@/components/core/text-editor/text-editor'; +import { toast } from '@/components/core/toaster'; + +import type { CreateFormProps } from './type'; + +const schema = zod.object({ + cat_name: zod.string().min(1, 'name-is-required').max(255), + cat_image: zod.array(zod.any()).optional(), + pos: zod.number().min(1, 'position is required').max(99), + init_answer: zod.string().optional(), + visible: zod.string(), + slug: zod.string().min(1, 'slug-is-required').max(255), + remarks: zod.string().optional(), + description: zod.string().optional(), + // NOTE: for image handling + avatar: zod.string().optional(), + // TODO: remove me + type: zod.string().optional(), + isActive: zod.boolean().optional(), + order: zod.number().optional(), +}); + +type Values = zod.infer; + +export const defaultValues = { + cat_name: '', + cat_image: undefined, + pos: 1, + init_answer: '', + visible: 'hidden', + slug: '', + remarks: '', + description: '', +} satisfies Values; + +export function MfQuestionCreateForm(): React.JSX.Element { + const router = useRouter(); + const { t } = useTranslation(['lp_categories']); + + const [isCreating, setIsCreating] = React.useState(false); + + const { + control, + handleSubmit, + formState: { errors }, + setValue, + watch, + } = useForm({ defaultValues, resolver: zodResolver(schema) }); + + const onSubmit = React.useCallback( + async (values: Values): Promise => { + setIsCreating(true); + + const payload: CreateFormProps = { + cat_name: values.cat_name, + cat_image: values.avatar ? [await base64ToFile(values.avatar)] : null, + pos: values.pos, + init_answer: values.init_answer, + visible: values.visible, + slug: values.slug, + remarks: values.remarks, + description: values.description, + // + // TODO: remove me + type: 'type tet', + isActive: true, + order: 1, + }; + + try { + const result = await pb.collection(COL_QUIZ_MF_CATEGORIES).create(payload); + + logger.debug(result); + toast.success(t('create.success')); + router.push(paths.dashboard.mf_categories.list); + } catch (error) { + logger.error(error); + toast.error(t('create.failed')); + } finally { + setIsCreating(false); + } + }, + // t is not necessary here + // eslint-disable-next-line react-hooks/exhaustive-deps + [router] + ); + + const avatarInputRef = React.useRef(null); + const avatar = watch('avatar'); + + const handleAvatarChange = React.useCallback( + async (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + + if (file) { + const url = await fileToBase64(file); + setValue('avatar', url); + } + }, + [setValue] + ); + + return ( +
+ + + } + spacing={4} + > + + {t('create.basic-info')} + + + + + + + + + + {t('create.avatar')} + {t('create.avatarRequirements')} + + + + + + + ( + + {t('create.cat_name')} + + {errors.cat_name ? {errors.cat_name.message} : null} + + )} + /> + + {/* */} + + ( + + {t('create.pos')} + { + field.onChange(parseInt(e.target.value)); + }} + type="number" + /> + {errors.pos ? {errors.pos.message} : null} + + )} + /> + + {/* */} + + ( + + {t('create.slug')} + + {errors.slug ? {errors.slug.message} : null} + + )} + /> + + {/* */} + + ( + + {t('edit.visible')} + + + {errors.visible ? {errors.visible.message} : null} + + )} + /> + + {/* */} + + ( + + {t('create.init_answer')} + + {errors.init_answer ? {errors.init_answer.message} : null} + + )} + /> + + + + {/* */} + + {t('create.detail-information')} + + + { + return ( + + + {t('create.description')} + + + { + field.onChange({ target: { value: editor.getHTML() } }); + }} + placeholder={t('create.description.default')} + /> + + + ); + }} + /> + + + ( + + + {t('create.remarks')} + + + { + field.onChange({ target: { value: editor.getText() } }); + }} + hideToolbar + placeholder={t('create.remarks.default')} + /> + + + )} + /> + + + + {/* */} + + + + + + + {t('create.createButton')} + + + + +
{JSON.stringify({ errors }, null, 2)}
+
+
+ ); +} diff --git a/002_source/cms/src/components/dashboard/mf/questions/mf-question-edit-form.tsx b/002_source/cms/src/components/dashboard/mf/questions/mf-question-edit-form.tsx new file mode 100644 index 0000000..a538bfa --- /dev/null +++ b/002_source/cms/src/components/dashboard/mf/questions/mf-question-edit-form.tsx @@ -0,0 +1,500 @@ +'use client'; + +import * as React from 'react'; +import RouterLink from 'next/link'; +import { useParams, useRouter } from 'next/navigation'; +// +import { COL_QUIZ_MF_CATEGORIES } from '@/constants'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { LoadingButton } from '@mui/lab'; +// +import Avatar from '@mui/material/Avatar'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Card from '@mui/material/Card'; +import CardActions from '@mui/material/CardActions'; +import CardContent from '@mui/material/CardContent'; +import Divider from '@mui/material/Divider'; +import FormControl from '@mui/material/FormControl'; +import FormHelperText from '@mui/material/FormHelperText'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import OutlinedInput from '@mui/material/OutlinedInput'; +import Select from '@mui/material/Select'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import Grid from '@mui/material/Unstable_Grid2'; +// +import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera'; +// +import { Controller, useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { z as zod } from 'zod'; + +import { paths } from '@/paths'; +import { logger } from '@/lib/default-logger'; +import { base64ToFile, fileToBase64 } from '@/lib/file-to-base64'; +import { pb } from '@/lib/pb'; +import { TextEditor } from '@/components/core/text-editor/text-editor'; +import { toast } from '@/components/core/toaster'; +import FormLoading from '@/components/loading'; + +import ErrorDisplay from '../../error'; +import type { EditFormProps } from './type'; + +// TODO: review this +const schema = zod.object({ + cat_name: zod.string().min(1, 'name-is-required').max(255), + // accept file object when user change image + // accept http string when user not changing image + cat_image: zod.union([zod.array(zod.any()), zod.string()]).optional(), + + // position + pos: zod.number().min(1, 'position is required').max(99), + + // it should be a valid JSON + init_answer: zod + .string() + .refine( + (value) => { + try { + JSON.parse(value); + return true; + } catch (error) { + return false; + } + }, + { message: 'init_answer must be a valid JSON' } + ) + .optional(), + visible: zod.string(), + slug: zod.string().min(0, 'slug-is-required').max(255).optional(), + remarks: zod.string().optional(), + description: zod.string().optional(), + // NOTE: for image handling + avatar: zod.string().optional(), +}); + +type Values = zod.infer; + +const defaultValues = { + cat_name: '', + cat_image: undefined, + pos: 1, + init_answer: JSON.stringify({}), + visible: 'hidden', + slug: '', + remarks: '', + description: '', +} satisfies Values; + +export function MfQuestionEditForm(): React.JSX.Element { + const router = useRouter(); + const { t } = useTranslation(['lp_categories']); + + const { cat_id: catId } = useParams<{ cat_id: string }>(); + // + const [isUpdating, setIsUpdating] = React.useState(false); + const [showLoading, setShowLoading] = React.useState(false); + // + const [showError, setShowError] = React.useState({ show: false, detail: '' }); + + const { + control, + handleSubmit, + formState: { errors }, + setValue, + reset, + watch, + } = useForm({ defaultValues, resolver: zodResolver(schema) }); + + const onSubmit = React.useCallback( + async (values: Values): Promise => { + setIsUpdating(true); + + const tempUpdate: EditFormProps = { + cat_name: values.cat_name, + cat_image: values.avatar ? [await base64ToFile(values.avatar)] : null, + pos: values.pos, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + init_answer: JSON.parse(values.init_answer || '{}'), + + visible: values.visible, + slug: values.slug || 'not-defined', + remarks: values.remarks, + description: values.description, + // + // TODO: remove below + type: '', + }; + + try { + const result = await pb.collection(COL_QUIZ_MF_CATEGORIES).update(catId, tempUpdate); + logger.debug(result); + toast.success(t('edit.success')); + router.push(paths.dashboard.mf_categories.list); + } catch (error) { + logger.error(error); + toast.error(t('update.failed')); + } finally { + setIsUpdating(false); + } + }, + // t is not necessary here + // eslint-disable-next-line react-hooks/exhaustive-deps + [router] + ); + + const avatarInputRef = React.useRef(null); + const avatar = watch('avatar'); + + const handleAvatarChange = React.useCallback( + async (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + + if (file) { + const url = await fileToBase64(file); + setValue('avatar', url); + } + }, + [setValue] + ); + + // TODO: need to align with save form + // use trycatch + const [textDescription, setTextDescription] = React.useState(''); + const [textRemarks, setTextRemarks] = React.useState(''); + + // load existing data when user arrive + const loadExistingData = React.useCallback( + async (id: string) => { + setShowLoading(true); + + try { + const result = await pb.collection(COL_QUIZ_MF_CATEGORIES).getOne(id); + + reset({ ...defaultValues, ...result, init_answer: JSON.stringify(result.init_answer) }); + setTextDescription(result.description); + setTextRemarks(result.remarks); + + if (result.cat_image !== '') { + const fetchResult = await fetch( + `http://127.0.0.1:8090/api/files/${result.collectionId}/${result.id}/${result.cat_image}` + ); + + const blob = await fetchResult.blob(); + const url = await fileToBase64(blob); + + setValue('avatar', url); + } else { + setValue('avatar', ''); + } + } catch (error) { + logger.error(error); + toast(t('list.error')); + + setShowError({ show: true, detail: JSON.stringify(error, null, 2) }); + } finally { + setShowLoading(false); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [catId] + ); + + React.useEffect(() => { + void loadExistingData(catId); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [catId]); + + if (showLoading) return ; + if (showError.show) + return ( + + ); + + return ( +
+ + + } + spacing={4} + > + + {t('edit.basic-info')} + + + + + + + + + + {t('edit.avatar')} + {t('edit.avatarRequirements')} + + + + + + + ( + + {t('edit.cat_name')} + + {errors.cat_name ? {errors.cat_name.message} : null} + + )} + /> + + {/* */} + + ( + + {t('edit.pos')} + { + field.onChange(parseInt(e.target.value)); + }} + type="number" + /> + {errors.pos ? {errors.pos.message} : null} + + )} + /> + + {/* */} + + ( + + {t('edit.slug')} + + {errors.slug ? {errors.slug.message} : null} + + )} + /> + + {/* */} + + ( + + {t('edit.visible')} + + + {errors.visible ? {errors.visible.message} : null} + + )} + /> + + {/* */} + + ( + + {t('edit.init_answer')} + + {errors.init_answer ? {errors.init_answer.message} : null} + + )} + /> + + + + {/* */} + + {t('edit.detail-information')} + + + { + return ( + + + {t('edit.description')} + + + { + field.onChange({ target: { value: editor.getHTML() } }); + }} + placeholder={t('edit.description.default')} + /> + + + ); + }} + /> + + + ( + + + {t('edit.remarks')} + + + { + field.onChange({ target: { value: editor.getText() } }); + }} + hideToolbar + placeholder={t('edit.remarks.default')} + /> + + + )} + /> + + + + {/* */} + + + + + + + {t('edit.updateButton')} + + + +
+ ); +} diff --git a/002_source/cms/src/components/dashboard/mf/questions/lp-questions-filters.tsx b/002_source/cms/src/components/dashboard/mf/questions/mf-questions-filters.tsx similarity index 97% rename from 002_source/cms/src/components/dashboard/mf/questions/lp-questions-filters.tsx rename to 002_source/cms/src/components/dashboard/mf/questions/mf-questions-filters.tsx index d5b5486..7c82f66 100644 --- a/002_source/cms/src/components/dashboard/mf/questions/lp-questions-filters.tsx +++ b/002_source/cms/src/components/dashboard/mf/questions/mf-questions-filters.tsx @@ -23,8 +23,8 @@ import { paths } from '@/paths'; import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button'; import { Option } from '@/components/core/option'; -import { useLpQuestionsSelection } from './lp-questions-selection-context'; -import { LpQuestion } from './type'; +import { useLpQuestionsSelection } from './mf-questions-selection-context'; +import { MfQuestion } from './type'; export interface Filters { email?: string; @@ -40,10 +40,10 @@ export type SortDir = 'asc' | 'desc'; export interface LpQuestionsFiltersProps { filters?: Filters; sortDir?: SortDir; - fullData: LpQuestion[]; + fullData: MfQuestion[]; } -export function LpQuestionsFilters({ +export function MfQuestionsFilters({ filters = {}, sortDir = 'desc', fullData, @@ -60,13 +60,13 @@ export function LpQuestionsFilters({ const selection = useLpQuestionsSelection(); function getVisible(): number { - return fullData.reduce((count, item: LpQuestion) => { + return fullData.reduce((count, item: MfQuestion) => { return item.visible === 'visible' ? count + 1 : count; }, 0); } function getHidden(): number { - return fullData.reduce((count, item: LpQuestion) => { + return fullData.reduce((count, item: MfQuestion) => { return item.visible === 'hidden' ? count + 1 : count; }, 0); } diff --git a/002_source/cms/src/components/dashboard/mf/questions/lp-questions-pagination.tsx b/002_source/cms/src/components/dashboard/mf/questions/mf-questions-pagination.tsx similarity index 96% rename from 002_source/cms/src/components/dashboard/mf/questions/lp-questions-pagination.tsx rename to 002_source/cms/src/components/dashboard/mf/questions/mf-questions-pagination.tsx index deef393..6f1a7f1 100644 --- a/002_source/cms/src/components/dashboard/mf/questions/lp-questions-pagination.tsx +++ b/002_source/cms/src/components/dashboard/mf/questions/mf-questions-pagination.tsx @@ -16,7 +16,7 @@ interface LessonQuestionsPaginationProps { rowsPerPage: number; } -export function LpQuestionsPagination({ +export function MfQuestionsPagination({ count, page, // diff --git a/002_source/cms/src/components/dashboard/mf/questions/lp-questions-selection-context.tsx b/002_source/cms/src/components/dashboard/mf/questions/mf-questions-selection-context.tsx similarity index 80% rename from 002_source/cms/src/components/dashboard/mf/questions/lp-questions-selection-context.tsx rename to 002_source/cms/src/components/dashboard/mf/questions/mf-questions-selection-context.tsx index 2cde8d7..21680c5 100644 --- a/002_source/cms/src/components/dashboard/mf/questions/lp-questions-selection-context.tsx +++ b/002_source/cms/src/components/dashboard/mf/questions/mf-questions-selection-context.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { useSelection } from '@/hooks/use-selection'; import type { Selection } from '@/hooks/use-selection'; -import type { LpQuestion } from './type'; +import type { MfQuestion } from './type'; function noop(): void { return undefined; @@ -26,14 +26,14 @@ export const LpQuestionsSelectionContext = React.createContext lessonCategories.map((customer) => customer.id), [lessonCategories]); + const customerIds = React.useMemo(() => lessonQuestions.map((customer) => customer.id), [lessonQuestions]); const selection = useSelection(customerIds); return ( diff --git a/002_source/cms/src/components/dashboard/mf/questions/lp-questions-table.tsx b/002_source/cms/src/components/dashboard/mf/questions/mf-questions-table.tsx similarity index 94% rename from 002_source/cms/src/components/dashboard/mf/questions/lp-questions-table.tsx rename to 002_source/cms/src/components/dashboard/mf/questions/mf-questions-table.tsx index e78356c..412aba8 100644 --- a/002_source/cms/src/components/dashboard/mf/questions/lp-questions-table.tsx +++ b/002_source/cms/src/components/dashboard/mf/questions/mf-questions-table.tsx @@ -25,10 +25,10 @@ import { DataTable } from '@/components/core/data-table'; import type { ColumnDef } from '@/components/core/data-table'; import ConfirmDeleteModal from './confirm-delete-modal'; -import { useLpQuestionsSelection } from './lp-questions-selection-context'; -import type { LpQuestion } from './type'; +import { useLpQuestionsSelection } from './mf-questions-selection-context'; +import type { MfQuestion } from './type'; -function columns(handleDeleteClick: (testId: string) => void): ColumnDef[] { +function columns(handleDeleteClick: (testId: string) => void): ColumnDef[] { return [ { formatter: (row): React.JSX.Element => ( @@ -40,7 +40,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef @@ -163,7 +163,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef @@ -187,11 +187,11 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef void; } -export function LpQuestionsTable({ rows, reloadRows }: LessonQuestionsTableProps): React.JSX.Element { +export function MfQuestionsTable({ rows, reloadRows }: LessonQuestionsTableProps): React.JSX.Element { const { t } = useTranslation(['lp_categories']); const { deselectAll, deselectOne, selectAll, selectOne, selected } = useLpQuestionsSelection(); @@ -211,7 +211,7 @@ export function LpQuestionsTable({ rows, reloadRows }: LessonQuestionsTableProps reloadRows={reloadRows} setOpen={setOpen} /> - + columns={columns(handleDeleteClick)} onDeselectAll={deselectAll} onDeselectOne={(_, row) => { diff --git a/002_source/cms/src/components/dashboard/mf/questions/type.d.ts b/002_source/cms/src/components/dashboard/mf/questions/type.d.ts index 8975fb4..ead5a47 100644 --- a/002_source/cms/src/components/dashboard/mf/questions/type.d.ts +++ b/002_source/cms/src/components/dashboard/mf/questions/type.d.ts @@ -1,4 +1,4 @@ -export interface LpQuestion { +export interface MfQuestion { isEmpty?: boolean; // id: string; diff --git a/002_source/cms/src/constants.ts b/002_source/cms/src/constants.ts index 621edae..eaf33ab 100644 --- a/002_source/cms/src/constants.ts +++ b/002_source/cms/src/constants.ts @@ -15,6 +15,7 @@ const COL_QUIZ_LP_CATEGORIES = 'QuizLPCategories'; const COL_QUIZ_LP_QUESTIONS = 'QuizLPQuestions'; // const COL_QUIZ_MF_CATEGORIES = 'QuizMFCategories'; +const COL_QUIZ_MF_QUESTIONS = 'QuizMFQuestions'; export { COL_LESSON_TYPES, @@ -29,5 +30,6 @@ export { COL_QUIZ_LP_QUESTIONS, // COL_QUIZ_MF_CATEGORIES, + COL_QUIZ_MF_QUESTIONS, // };