From b963a85cc6b6afcd08f78e046f3d7071b516e88f Mon Sep 17 00:00:00 2001 From: louiscklaw Date: Sun, 20 Apr 2025 02:00:25 +0800 Subject: [PATCH] update build ok, --- 002_source/cms/public/locales/dev/PROMPT.md | 17 + .../locales/dev/listening_practice.json | 39 ++- .../app/dashboard/Sample/BasicDetailCard.tsx | 79 +++++ .../src/app/dashboard/Sample/Helloworld.tsx | 14 + .../dashboard/Sample/SampleAddressCard.tsx | 80 +++++ .../SampleAddresses.tsx | 0 .../SampleNotifications.tsx | 0 .../dashboard/Sample/SamplePaymentCard.tsx | 95 ++++++ .../SamplePayments.tsx | 0 .../dashboard/Sample/SampleSecurityCard.tsx | 41 +++ .../app/dashboard/Sample/SampleTitleCard.tsx | 44 +++ .../lesson_types/[type_id]/SamplePayments.tsx | 43 +++ .../dashboard/lesson_types/[type_id]/page.tsx | 293 ++---------------- .../src/app/dashboard/lesson_types/page.tsx | 10 +- .../lp_categories/[customerId]/page.tsx | 236 -------------- .../lp_categories/[lp_cat_id]/page.tsx | 121 ++++++++ .../src/app/dashboard/lp_categories/page.tsx | 146 +++++++-- .../src/components/dashboard/error/index.tsx | 8 +- .../lesson_type/lesson-types-filters.tsx | 23 +- .../dashboard/lp_categories/_constants.ts | 41 +++ .../lp_categories/lp-categories-filters.tsx | 218 ++++++++++--- .../lp-categories-pagination.tsx | 27 +- .../lp_categories/lp-categories-table.tsx | 80 +---- 002_source/cms/src/constants.ts | 1 + 002_source/cms/src/db/DB_AI_GUIDELINE.MD | 44 ++- .../cms/src/db/QuizListenings/GetById.tsx | 8 + .../src/db/QuizListenings/GetHiddenCount.tsx | 14 + .../src/db/QuizListenings/GetVisibleCount.tsx | 14 + .../src/db/QuizListenings/ListWithOption.tsx | 22 ++ 002_source/cms/src/types/LpCategory.tsx | 70 ++++- 30 files changed, 1133 insertions(+), 695 deletions(-) create mode 100644 002_source/cms/src/app/dashboard/Sample/BasicDetailCard.tsx create mode 100644 002_source/cms/src/app/dashboard/Sample/Helloworld.tsx create mode 100644 002_source/cms/src/app/dashboard/Sample/SampleAddressCard.tsx rename 002_source/cms/src/app/dashboard/{lp_categories/[customerId] => Sample}/SampleAddresses.tsx (100%) rename 002_source/cms/src/app/dashboard/{lp_categories/[customerId] => Sample}/SampleNotifications.tsx (100%) create mode 100644 002_source/cms/src/app/dashboard/Sample/SamplePaymentCard.tsx rename 002_source/cms/src/app/dashboard/{lp_categories/[customerId] => Sample}/SamplePayments.tsx (100%) create mode 100644 002_source/cms/src/app/dashboard/Sample/SampleSecurityCard.tsx create mode 100644 002_source/cms/src/app/dashboard/Sample/SampleTitleCard.tsx create mode 100644 002_source/cms/src/app/dashboard/lesson_types/[type_id]/SamplePayments.tsx delete mode 100644 002_source/cms/src/app/dashboard/lp_categories/[customerId]/page.tsx create mode 100644 002_source/cms/src/app/dashboard/lp_categories/[lp_cat_id]/page.tsx create mode 100644 002_source/cms/src/components/dashboard/lp_categories/_constants.ts create mode 100644 002_source/cms/src/db/QuizListenings/GetById.tsx create mode 100644 002_source/cms/src/db/QuizListenings/GetHiddenCount.tsx create mode 100644 002_source/cms/src/db/QuizListenings/GetVisibleCount.tsx create mode 100644 002_source/cms/src/db/QuizListenings/ListWithOption.tsx diff --git a/002_source/cms/public/locales/dev/PROMPT.md b/002_source/cms/public/locales/dev/PROMPT.md index 8fb9ed5..1e8acb1 100644 --- a/002_source/cms/public/locales/dev/PROMPT.md +++ b/002_source/cms/public/locales/dev/PROMPT.md @@ -11,6 +11,7 @@ dashboard.lessonCategories.edit.name dashboard.lessonCategories.edit.type ``` + --- please read `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/components/dashboard/lesson_category/lesson-category-edit-form.tsx` @@ -20,3 +21,19 @@ and update `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/letter please refactor `common.json` to smaller translation files, thanks e.g. `lessonTypes` -> `lesson_type` + +--- + +Hi, i want you to help merge two translation files. + +base_dir=`/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/public/locales/dev` + +I want you to merge the content + +from `/lesson_type.json` (source file) +to `/listening_practice.json` (dest file) + +please extract , link up and remember the document properties +(e.g. types, functions, variables, constants, etc) + +update the variables and properties of dest file to reflect `listening practice categories`/`lp_categories` diff --git a/002_source/cms/public/locales/dev/listening_practice.json b/002_source/cms/public/locales/dev/listening_practice.json index f79da97..352e2b2 100644 --- a/002_source/cms/public/locales/dev/listening_practice.json +++ b/002_source/cms/public/locales/dev/listening_practice.json @@ -1,5 +1,36 @@ { - "hello": "world", - "add": "新加", - "listening-practice": "聽力練習" -} + "add": "新增", + "listening-practice": "聽力練習", + "list": { + "title": "聽力練習分類列表", + "empty": "沒有找到聽力練習分類", + "action": "動作?", + "basic-details": "基本資料" + }, + "helloworld": "listening_practice", + "title": "聽力練習分類", + "type": "聽力練習分類", + "error": { + "invalid": "無效的聽力練習分類", + "not_found": "未找到聽力練習分類" + }, + "create": { + "title": "創建聽力練習分類", + "success": "聽力練習分類創建成功", + "error": "創建聽力練習分類時出錯" + }, + "edit": { + "title": "編輯聽力練習分類", + "success": "聽力練習分類更新成功", + "error": "更新聽力練習分類時出錯" + }, + "delete": { + "title": "刪除聽力練習分類", + "confirm": "確定要刪除聽力練習分類嗎?", + "success": "聽力練習分類刪除成功", + "error": "刪除聽力練習分類時出錯" + }, + "view": { + "title": "查看聽力練習分類詳情" + } +} \ No newline at end of file diff --git a/002_source/cms/src/app/dashboard/Sample/BasicDetailCard.tsx b/002_source/cms/src/app/dashboard/Sample/BasicDetailCard.tsx new file mode 100644 index 0000000..bc9e4d4 --- /dev/null +++ b/002_source/cms/src/app/dashboard/Sample/BasicDetailCard.tsx @@ -0,0 +1,79 @@ +'use client'; + +import * as React from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import Avatar from '@mui/material/Avatar'; +import Card from '@mui/material/Card'; +import CardHeader from '@mui/material/CardHeader'; +import Chip from '@mui/material/Chip'; +import Divider from '@mui/material/Divider'; +import IconButton from '@mui/material/IconButton'; +import LinearProgress from '@mui/material/LinearProgress'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple'; +import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User'; +import { useTranslation } from 'react-i18next'; + +import { paths } from '@/paths'; +import { PropertyItem } from '@/components/core/property-item'; +import { PropertyList } from '@/components/core/property-list'; + +export default function BasicDetailCard({ + lpCatId, + handleEditClick, +}: { + lpCatId: string; + handleEditClick: () => void; +}): React.JSX.Element { + const { t } = useTranslation(); + const router = useRouter(); + + return ( + + { + handleEditClick(); + }} + > + + + } + avatar={ + + + + } + title={t('list.basic-details')} + /> + } orientation="vertical" sx={{ '--PropertyItem-padding': '12px 24px' }}> + {( + [ + { key: 'Customer ID', value: }, + { key: 'Name', value: 'Miron Vitold' }, + { key: 'Email', value: 'miron.vitold@domain.com' }, + { key: 'Phone', value: '(425) 434-5535' }, + { key: 'Company', value: 'Devias IO' }, + { + key: 'Quota', + value: ( + + + + 50% + + + ), + }, + ] satisfies { key: string; value: React.ReactNode }[] + ).map( + (item): React.JSX.Element => ( + + ) + )} + + + ); +} diff --git a/002_source/cms/src/app/dashboard/Sample/Helloworld.tsx b/002_source/cms/src/app/dashboard/Sample/Helloworld.tsx new file mode 100644 index 0000000..bdf7211 --- /dev/null +++ b/002_source/cms/src/app/dashboard/Sample/Helloworld.tsx @@ -0,0 +1,14 @@ +'use client'; + +import * as React from 'react'; +import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; + +function Page(): React.JSX.Element { + React.useLayoutEffect(() => { + console.log('helloworld'); + }, []); + + return <>helloworld; +} + +export default Page; diff --git a/002_source/cms/src/app/dashboard/Sample/SampleAddressCard.tsx b/002_source/cms/src/app/dashboard/Sample/SampleAddressCard.tsx new file mode 100644 index 0000000..6a0462b --- /dev/null +++ b/002_source/cms/src/app/dashboard/Sample/SampleAddressCard.tsx @@ -0,0 +1,80 @@ +'use client'; + +import * as React from 'react'; +import RouterLink from 'next/link'; +import { useParams, useRouter } from 'next/navigation'; +import getLessonCategoryById from '@/db/LessonCategories/GetById'; +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 CardContent from '@mui/material/CardContent'; +import CardHeader from '@mui/material/CardHeader'; +import Chip from '@mui/material/Chip'; +import Divider from '@mui/material/Divider'; +import IconButton from '@mui/material/IconButton'; +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 Grid from '@mui/material/Unstable_Grid2'; +import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft'; +import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown'; +import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; +import { CreditCard as CreditCardIcon } from '@phosphor-icons/react/dist/ssr/CreditCard'; +import { House as HouseIcon } from '@phosphor-icons/react/dist/ssr/House'; +import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple'; +import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus'; +import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning'; +import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User'; +import type { RecordModel } from 'pocketbase'; +import { useTranslation } from 'react-i18next'; + +import type { Address } from '@/types/Address'; +import { LpCategory } from '@/types/LpCategory'; +import { paths } from '@/paths'; +import { dayjs } from '@/lib/dayjs'; +import { logger } from '@/lib/default-logger'; +import { pb } from '@/lib/pb'; +import { PropertyItem } from '@/components/core/property-item'; +import { PropertyList } from '@/components/core/property-list'; +import { toast } from '@/components/core/toaster'; +import ErrorDisplay from '@/components/dashboard/error'; +import LpCategoryDefaultValue, { defaultLpCategory } from '@/components/dashboard/lp_categories/_constants'; +import { Notifications } from '@/components/dashboard/lp_categories/notifications'; +import { Payments } from '@/components/dashboard/lp_categories/payments'; +import { ShippingAddress } from '@/components/dashboard/lp_categories/shipping-address'; + +import { SampleAddresses } from './SampleAddresses'; +import { SampleNotifications } from './SampleNotifications'; +import { SamplePayments } from './SamplePayments'; + +export default function SampleAddressCard(): React.JSX.Element { + const { t } = useTranslation(); + return ( + + }> + {t('list.add')} + + } + avatar={ + + + + } + title={t('list.shipping-addresses')} + /> + + + {(SampleAddresses satisfies Address[]).map((address) => ( + + + + ))} + + + + ); +} diff --git a/002_source/cms/src/app/dashboard/lp_categories/[customerId]/SampleAddresses.tsx b/002_source/cms/src/app/dashboard/Sample/SampleAddresses.tsx similarity index 100% rename from 002_source/cms/src/app/dashboard/lp_categories/[customerId]/SampleAddresses.tsx rename to 002_source/cms/src/app/dashboard/Sample/SampleAddresses.tsx diff --git a/002_source/cms/src/app/dashboard/lp_categories/[customerId]/SampleNotifications.tsx b/002_source/cms/src/app/dashboard/Sample/SampleNotifications.tsx similarity index 100% rename from 002_source/cms/src/app/dashboard/lp_categories/[customerId]/SampleNotifications.tsx rename to 002_source/cms/src/app/dashboard/Sample/SampleNotifications.tsx diff --git a/002_source/cms/src/app/dashboard/Sample/SamplePaymentCard.tsx b/002_source/cms/src/app/dashboard/Sample/SamplePaymentCard.tsx new file mode 100644 index 0000000..bac97bf --- /dev/null +++ b/002_source/cms/src/app/dashboard/Sample/SamplePaymentCard.tsx @@ -0,0 +1,95 @@ +'use client'; + +import * as React from 'react'; +import RouterLink from 'next/link'; +import { useParams, useRouter } from 'next/navigation'; +import getLessonCategoryById from '@/db/LessonCategories/GetById'; +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 CardContent from '@mui/material/CardContent'; +import CardHeader from '@mui/material/CardHeader'; +import Chip from '@mui/material/Chip'; +import Divider from '@mui/material/Divider'; +import IconButton from '@mui/material/IconButton'; +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 Grid from '@mui/material/Unstable_Grid2'; +import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft'; +import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown'; +import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; +import { CreditCard as CreditCardIcon } from '@phosphor-icons/react/dist/ssr/CreditCard'; +import { House as HouseIcon } from '@phosphor-icons/react/dist/ssr/House'; +import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple'; +import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus'; +import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning'; +import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User'; +import type { RecordModel } from 'pocketbase'; +import { useTranslation } from 'react-i18next'; + +import type { Address } from '@/types/Address'; +import { LpCategory } from '@/types/LpCategory'; +import { paths } from '@/paths'; +import { dayjs } from '@/lib/dayjs'; +import { logger } from '@/lib/default-logger'; +import { pb } from '@/lib/pb'; +import { PropertyItem } from '@/components/core/property-item'; +import { PropertyList } from '@/components/core/property-list'; +import { toast } from '@/components/core/toaster'; +import ErrorDisplay from '@/components/dashboard/error'; +import LpCategoryDefaultValue, { defaultLpCategory } from '@/components/dashboard/lp_categories/_constants'; +import { Notifications } from '@/components/dashboard/lp_categories/notifications'; +import { Payments } from '@/components/dashboard/lp_categories/payments'; +import { ShippingAddress } from '@/components/dashboard/lp_categories/shipping-address'; + +import SampleAddressCard from './SampleAddressCard'; +import { SampleAddresses } from './SampleAddresses'; +import { SampleNotifications } from './SampleNotifications'; +import { SamplePayments } from './SamplePayments'; + +export default function SamplePaymentCard(): React.JSX.Element { + const { t } = useTranslation(); + return ( + <> + + + }> + {t('list.edit')} + + } + avatar={ + + + + } + title={t('list.billing-details')} + /> + + + } sx={{ '--PropertyItem-padding': '16px' }}> + {( + [ + { key: t('Credit card'), value: '**** 4142' }, + { key: t('Country'), value: t('United States') }, + { key: t('State'), value: t('Michigan') }, + { key: t('City'), value: t('Southfield') }, + { key: t('Address'), value: t('Address') }, + { key: t('Tax ID'), value: t('Tax ID') }, + ] satisfies { key: string; value: React.ReactNode }[] + ).map( + (item): React.JSX.Element => ( + + ) + )} + + + + + + ); +} diff --git a/002_source/cms/src/app/dashboard/lp_categories/[customerId]/SamplePayments.tsx b/002_source/cms/src/app/dashboard/Sample/SamplePayments.tsx similarity index 100% rename from 002_source/cms/src/app/dashboard/lp_categories/[customerId]/SamplePayments.tsx rename to 002_source/cms/src/app/dashboard/Sample/SamplePayments.tsx diff --git a/002_source/cms/src/app/dashboard/Sample/SampleSecurityCard.tsx b/002_source/cms/src/app/dashboard/Sample/SampleSecurityCard.tsx new file mode 100644 index 0000000..31f9f77 --- /dev/null +++ b/002_source/cms/src/app/dashboard/Sample/SampleSecurityCard.tsx @@ -0,0 +1,41 @@ +'use client'; + +import * as React from 'react'; +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import CardHeader from '@mui/material/CardHeader'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning'; +import { useTranslation } from 'react-i18next'; + +export default function SampleSecurityCard(): React.JSX.Element { + const { t } = useTranslation(); + + return ( + + + + + } + title={t('list.security')} + /> + + +
+ +
+ + {t('a-deleted-customer-cannot-be-restored-all-data-will-be-permanently-removed')} + +
+
+
+ ); +} diff --git a/002_source/cms/src/app/dashboard/Sample/SampleTitleCard.tsx b/002_source/cms/src/app/dashboard/Sample/SampleTitleCard.tsx new file mode 100644 index 0000000..62d0bfe --- /dev/null +++ b/002_source/cms/src/app/dashboard/Sample/SampleTitleCard.tsx @@ -0,0 +1,44 @@ +'use client'; + +import * as React from 'react'; +import { Button } from '@mui/material'; +import Avatar from '@mui/material/Avatar'; +import Chip from '@mui/material/Chip'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown'; +import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; +import { useTranslation } from 'react-i18next'; + +export default function SampleTitleCard(): React.JSX.Element { + const { t } = useTranslation(); + + return ( + <> + + + empty + +
+ + {t('list.customer-name')} + } + label={t('list.active')} + size="small" + variant="outlined" + /> + + + {t('list.customer-email')} + +
+
+
+ +
+ + ); +} diff --git a/002_source/cms/src/app/dashboard/lesson_types/[type_id]/SamplePayments.tsx b/002_source/cms/src/app/dashboard/lesson_types/[type_id]/SamplePayments.tsx new file mode 100644 index 0000000..b9eba69 --- /dev/null +++ b/002_source/cms/src/app/dashboard/lesson_types/[type_id]/SamplePayments.tsx @@ -0,0 +1,43 @@ +'use client'; + +// import { dayjs } from 'dayjs'; +import type { Payment } from '@/types/Payment'; +import { dayjs } from '@/lib/dayjs'; + +export const SamplePayments: Payment[] = [ + { + currency: 'USD', + amount: 500, + invoiceId: 'INV-005', + status: 'completed', + createdAt: dayjs().subtract(5, 'minute').subtract(1, 'hour').toDate(), + }, + { + currency: 'USD', + amount: 324.5, + invoiceId: 'INV-004', + status: 'refunded', + createdAt: dayjs().subtract(21, 'minute').subtract(2, 'hour').toDate(), + }, + { + currency: 'USD', + amount: 746.5, + invoiceId: 'INV-003', + status: 'completed', + createdAt: dayjs().subtract(7, 'minute').subtract(3, 'hour').toDate(), + }, + { + currency: 'USD', + amount: 56.89, + invoiceId: 'INV-002', + status: 'completed', + createdAt: dayjs().subtract(48, 'minute').subtract(4, 'hour').toDate(), + }, + { + currency: 'USD', + amount: 541.59, + invoiceId: 'INV-001', + status: 'completed', + createdAt: dayjs().subtract(31, 'minute').subtract(5, 'hour').toDate(), + }, +]; diff --git a/002_source/cms/src/app/dashboard/lesson_types/[type_id]/page.tsx b/002_source/cms/src/app/dashboard/lesson_types/[type_id]/page.tsx index 72bb39e..04a4fd5 100644 --- a/002_source/cms/src/app/dashboard/lesson_types/[type_id]/page.tsx +++ b/002_source/cms/src/app/dashboard/lesson_types/[type_id]/page.tsx @@ -3,59 +3,42 @@ import * as React from 'react'; import RouterLink from 'next/link'; import { useParams, useRouter } from 'next/navigation'; -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 CardContent from '@mui/material/CardContent'; -import CardHeader from '@mui/material/CardHeader'; -import Chip from '@mui/material/Chip'; -import Divider from '@mui/material/Divider'; -import IconButton from '@mui/material/IconButton'; -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 Grid from '@mui/material/Unstable_Grid2'; import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft'; -import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown'; -import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; -import { CreditCard as CreditCardIcon } from '@phosphor-icons/react/dist/ssr/CreditCard'; -import { House as HouseIcon } from '@phosphor-icons/react/dist/ssr/House'; -import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple'; -import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus'; -import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning'; -import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User'; import type { RecordModel } from 'pocketbase'; import { useTranslation } from 'react-i18next'; import { paths } from '@/paths'; -import { dayjs } from '@/lib/dayjs'; import { logger } from '@/lib/default-logger'; import { pb } from '@/lib/pb'; -import { PropertyItem } from '@/components/core/property-item'; -import { PropertyList } from '@/components/core/property-list'; import { toast } from '@/components/core/toaster'; import ErrorDisplay from '@/components/dashboard/error'; import { defaultLessonType, LessonTypeDefaultValue } from '@/components/dashboard/lesson_type/_constants'; -// import { getLessonTypeById } from '@/components/dashboard/lesson_type/http-actions'; -// import { LessonTypeDefaultValue, type LessonType } from '@/components/dashboard/lesson_type/ILessonType'; -// import { defaultLessonType } from '@/components/dashboard/lesson_type/interfaces'; import { Notifications } from '@/components/dashboard/lesson_type/notifications'; -import { Payments } from '@/components/dashboard/lesson_type/payments'; -import type { Address } from '@/components/dashboard/lesson_type/shipping-address'; -import { ShippingAddress } from '@/components/dashboard/lesson_type/shipping-address'; import { type LessonType } from '@/components/dashboard/lesson_type/types'; import FormLoading from '@/components/loading'; +import BasicDetailCard from '../../Sample/BasicDetailCard'; +import SampleAddressCard from '../../Sample/SampleAddressCard'; +import { SampleNotifications } from '../../Sample/SampleNotifications'; +import SamplePaymentCard from '../../Sample/SamplePaymentCard'; +import SampleSecurityCard from '../../Sample/SampleSecurityCard'; +import SampleTitleCard from '../../Sample/SampleTitleCard'; + export default function Page(): React.JSX.Element { - const { t } = useTranslation(['common', 'lesson_type']); + const { t } = useTranslation(['lesson_type']); const router = useRouter(); // const { type_id: typeId } = useParams<{ type_id: string }>(); + // const [showLoading, setShowLoading] = React.useState(true); const [showError, setShowError] = React.useState(false); + const [errorDetails, setErrorDetails] = React.useState(''); + // const [showLessonType, setShowLessonType] = React.useState(LessonTypeDefaultValue); @@ -73,7 +56,7 @@ export default function Page(): React.JSX.Element { .catch((err) => { logger.error(err); toast(t('dashboard.lessonTypes.list.error')); - + setErrorDetails(err); setShowError(true); }) .finally(() => { @@ -89,7 +72,7 @@ export default function Page(): React.JSX.Element { ); @@ -113,261 +96,25 @@ export default function Page(): React.JSX.Element { variant="subtitle2" > - {t('list.title', { ns: 'lesson_type' })} + {t('list.title')} - - - empty - -
- - {showLessonType.name} - } - label={showLessonType.visible} - size="small" - variant="outlined" - /> - - - {showLessonType.id} - -
-
-
- -
+
- - { - handleEditClick(); - }} - > - - - } - avatar={ - - - - } - title={t('basic-details', { ns: 'lesson_type' })} - /> - } - orientation="vertical" - sx={{ '--PropertyItem-padding': '12px 24px' }} - > - {( - [ - { key: 'Customer ID', value: }, - { key: 'Name', value: showLessonType.name }, - { key: 'Type', value: showLessonType.type }, - { key: 'Pos', value: showLessonType.pos }, - { - key: 'Visible', - value: ( - - ), - }, - { - key: 'Quota', - value: ( - - - - 50% - - - ), - }, - ] satisfies { key: string; value: React.ReactNode }[] - ).map( - (item): React.JSX.Element => ( - - ) - )} - - - - - - - } - title={t('security', { ns: 'lesson_type' })} - /> - - -
- -
- - A deleted lesson type cannot be restored. All data will be permanently removed. - -
-
-
+ +
- - - }> - Edit - - } - avatar={ - - - - } - title={t('billing-details', { ns: 'lesson_type' })} - /> - - - } sx={{ '--PropertyItem-padding': '16px' }}> - {( - [ - { key: 'Credit card', value: '**** 4142' }, - { key: 'Country', value: 'United States' }, - { key: 'State', value: 'Michigan' }, - { key: 'City', value: 'Southfield' }, - { key: 'Address', value: '1721 Bartlett Avenue, 48034' }, - { key: 'Tax ID', value: 'EU87956621' }, - ] satisfies { key: string; value: React.ReactNode }[] - ).map( - (item): React.JSX.Element => ( - - ) - )} - - - - - - }> - Add - - } - avatar={ - - - - } - title={t('shipping-addresses', { ns: 'lesson_type' })} - /> - - - {( - [ - { - id: 'ADR-001', - country: 'United States', - state: 'Michigan', - city: 'Lansing', - zipCode: '48933', - street: '480 Haven Lane', - primary: true, - }, - { - id: 'ADR-002', - country: 'United States', - state: 'Missouri', - city: 'Springfield', - zipCode: '65804', - street: '4807 Lighthouse Drive', - }, - ] satisfies Address[] - ).map((address) => ( - - - - ))} - - - - + + +
diff --git a/002_source/cms/src/app/dashboard/lesson_types/page.tsx b/002_source/cms/src/app/dashboard/lesson_types/page.tsx index b02cca6..d3f264e 100644 --- a/002_source/cms/src/app/dashboard/lesson_types/page.tsx +++ b/002_source/cms/src/app/dashboard/lesson_types/page.tsx @@ -40,12 +40,9 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { const [showLoading, setShowLoading] = React.useState(true); const [showError, setShowError] = React.useState(false); const [rowsPerPage, setRowsPerPage] = React.useState(5); - // const [f, setF] = React.useState([]); const [currentPage, setCurrentPage] = React.useState(0); - // const [recordCount, setRecordCount] = React.useState(0); - const [listOption, setListOption] = React.useState({}); const [listSort, setListSort] = React.useState({}); @@ -143,10 +140,15 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element { filters={{ email, phone, status, name, visible, type }} fullData={lessonTypesData} sortDir={sortDir} + // /> - + - - -
- - - {t('Customers')} - -
- - - - MV - -
- - {t('Customer name')} - } - label={t('Active')} - size="small" - variant="outlined" - /> - - - {t('Customer email')} - -
-
-
- -
-
-
- - - - - - - - } - avatar={ - - - - } - title={t('Basic details')} - /> - } - orientation="vertical" - sx={{ '--PropertyItem-padding': '12px 24px' }} - > - {( - [ - { key: t('Customer ID'), value: }, - { key: t('Name'), value: t('Customer name') }, - { key: t('Email'), value: t('Customer email') }, - { key: t('Phone'), value: t('Customer phone') }, - { key: t('Company'), value: t('Company name') }, - { - key: t('Quota'), - value: ( - - - - 50% - - - ), - }, - ] satisfies { key: string; value: React.ReactNode }[] - ).map( - (item): React.JSX.Element => ( - - ) - )} - - - - - - - } - title={t('Security')} - /> - - -
- -
- - {t('A deleted customer cannot be restored. All data will be permanently removed.')} - -
-
-
-
-
- - - - - }> - {t('Edit')} - - } - avatar={ - - - - } - title={t('Billing details')} - /> - - - } sx={{ '--PropertyItem-padding': '16px' }}> - {( - [ - { key: t('Credit card'), value: '**** 4142' }, - { key: t('Country'), value: t('United States') }, - { key: t('State'), value: t('Michigan') }, - { key: t('City'), value: t('Southfield') }, - { key: t('Address'), value: t('Address') }, - { key: t('Tax ID'), value: t('Tax ID') }, - ] satisfies { key: string; value: React.ReactNode }[] - ).map( - (item): React.JSX.Element => ( - - ) - )} - - - - - - }> - {t('Add')} - - } - avatar={ - - - - } - title={t('Shipping addresses')} - /> - - - {(SampleAddresses satisfies Address[]).map((address) => ( - - - - ))} - - - - - - -
-
- - ); -} diff --git a/002_source/cms/src/app/dashboard/lp_categories/[lp_cat_id]/page.tsx b/002_source/cms/src/app/dashboard/lp_categories/[lp_cat_id]/page.tsx new file mode 100644 index 0000000..4ef7857 --- /dev/null +++ b/002_source/cms/src/app/dashboard/lp_categories/[lp_cat_id]/page.tsx @@ -0,0 +1,121 @@ +'use client'; + +import * as React from 'react'; +import RouterLink from 'next/link'; +import { useParams, useRouter } from 'next/navigation'; +import getQuizListeningById from '@/db/QuizListenings/GetById'; +import Box from '@mui/material/Box'; +import Link from '@mui/material/Link'; +import Stack from '@mui/material/Stack'; +import Grid from '@mui/material/Unstable_Grid2'; +import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft'; +import type { RecordModel } from 'pocketbase'; +import { useTranslation } from 'react-i18next'; + +import type { LpCategory } from '@/types/LpCategory'; +import { paths } from '@/paths'; +import { logger } from '@/lib/default-logger'; +import { toast } from '@/components/core/toaster'; +import ErrorDisplay from '@/components/dashboard/error'; +import { defaultLpCategory, LpCategoryDefaultValue } from '@/components/dashboard/lp_categories/_constants'; +import { Notifications } from '@/components/dashboard/lp_categories/notifications'; +import FormLoading from '@/components/loading'; + +import BasicDetailCard from '../../Sample/BasicDetailCard'; +import SampleAddressCard from '../../Sample/SampleAddressCard'; +import { SampleNotifications } from '../../Sample/SampleNotifications'; +import SamplePaymentCard from '../../Sample/SamplePaymentCard'; +import SampleSecurityCard from '../../Sample/SampleSecurityCard'; +import SampleTitleCard from '../../Sample/SampleTitleCard'; + +export default function Page(): React.JSX.Element { + const { t } = useTranslation(['listening_practice']); + const router = useRouter(); + // + const { lp_cat_id: lpCatId } = useParams<{ lp_cat_id: string }>(); + + // + const [showLoading, setShowLoading] = React.useState(true); + const [showError, setShowError] = React.useState(false); + const [errorDetails, setErrorDetails] = React.useState(''); + + // + const [showLessonType, setShowLessonType] = React.useState(LpCategoryDefaultValue.default); + + function handleEditClick(): void { + router.push(paths.dashboard.lp_categories.edit(lpCatId)); + } + + React.useEffect(() => { + getQuizListeningById(lpCatId) + .then((model: RecordModel) => { + setShowLessonType({ ...defaultLpCategory, ...model }); + }) + .catch((err) => { + logger.error(err); + toast(t('list.error')); + setErrorDetails(err); + setShowError(true); + }) + .finally(() => { + setShowLoading(false); + }); + }, [lpCatId]); + + if (showLoading) return ; + + if (showError) + return ( + + ); + + return ( + + + +
+ + + {t('list.title')} + +
+ + + +
+ + + + + + + + + + + + + + + +
+
+ ); +} 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 81f818b..302c3b4 100644 --- a/002_source/cms/src/app/dashboard/lp_categories/page.tsx +++ b/002_source/cms/src/app/dashboard/lp_categories/page.tsx @@ -2,35 +2,106 @@ import * as React from 'react'; import { useRouter } from 'next/navigation'; -import getAllQuizListenings from '@/db/QuizListenings/GetAll'; +import listWithOption from '@/db/QuizListenings/ListWithOption'; +import { LoadingButton } from '@mui/lab'; import Box from '@mui/material/Box'; -import Button from '@mui/material/Button'; import Card from '@mui/material/Card'; import Divider from '@mui/material/Divider'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus'; +import type { ListResult, RecordModel } from 'pocketbase'; import { useTranslation } from 'react-i18next'; import type { LpCategory } from '@/types/LpCategory'; import { paths } from '@/paths'; +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 { LpCategories } from './lp-categories'; +import FormLoading from '@/components/loading'; export default function Page({ searchParams }: PageProps): React.JSX.Element { - const router = useRouter(); const { t } = useTranslation(['listening_practice']); - const { email, phone, sortDir, status } = searchParams; + const { email, phone, sortDir, status, name, visible, type } = searchParams; + const router = useRouter(); + const [lpCategoriesData, setLpCategoriesData] = React.useState([]); + // - const sortedCustomers = applySort(LpCategories, sortDir); - const lpCategories = applyFilters(sortedCustomers, { email, phone, status }); + const [isLoadingAddPage, setIsLoadingAddPage] = React.useState(false); + const [showLoading, setShowLoading] = React.useState(true); + const [showError, setShowError] = React.useState(false); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + const [f, setF] = React.useState([]); + const [currentPage, setCurrentPage] = React.useState(0); + const [recordCount, setRecordCount] = React.useState(0); + const [listOption, setListOption] = React.useState({}); + const [listSort, setListSort] = React.useState({}); - React.useEffect(() => {}, []); + // + const reloadRows = async (): Promise => { + try { + const models: ListResult = await listWithOption({ + currentPage: currentPage + 1, + rowsPerPage, + listOption, + }); + + const { items, totalItems } = models; + const tempLpCategories: LpCategory[] = items.map((lt) => { + return { ...defaultLpCategory, ...lt }; + }); + + setLpCategoriesData(tempLpCategories); + setRecordCount(totalItems); + setF(tempLpCategories); + } catch (error) { + // + } finally { + setShowLoading(false); + } + }; + + React.useEffect(() => { + void reloadRows(); + }, [currentPage, rowsPerPage, listOption]); + + React.useEffect(() => { + let tempFilter = [], + tempSortDir = ''; + + if (visible) { + tempFilter.push(`visible = "${visible}"`); + } + + if (sortDir) { + tempSortDir = `-created`; + } + + if (name) { + tempFilter.push(`name ~ "%${name}%"`); + } + + if (type) { + tempFilter.push(`type ~ "%${type}%"`); + } + + setListOption({ + filter: tempFilter.join(' && '), + sort: tempSortDir, + // + }); + }, [visible, sortDir, name, type]); + + if (f.length === 0 || showLoading) return ; + + if (showError) + return ( + + ); return ( - {t('listening-practice')} + {t('list.title')} - + - + - + - + - + @@ -86,7 +173,7 @@ function applySort(row: LpCategory[], sortDir: 'asc' | 'desc' | undefined): LpCa }); } -function applyFilters(row: LpCategory[], { email, phone, status }: Filters): LpCategory[] { +function applyFilters(row: LpCategory[], { email, phone, status, name, visible }: Filters): LpCategory[] { return row.filter((item) => { if (email) { if (!item.email?.toLowerCase().includes(email.toLowerCase())) { @@ -106,10 +193,31 @@ function applyFilters(row: LpCategory[], { email, phone, status }: Filters): LpC } } + if (name) { + if (!item.name?.toLowerCase().includes(name.toLowerCase())) { + return false; + } + } + + if (visible) { + if (!item.visible?.toLowerCase().includes(visible.toLowerCase())) { + return false; + } + } + return true; }); } interface PageProps { - searchParams: { email?: string; phone?: string; sortDir?: 'asc' | 'desc'; status?: string }; + searchParams: { + email?: string; + phone?: string; + sortDir?: 'asc' | 'desc'; + status?: string; + name?: string; + visible?: string; + type?: string; + // + }; } diff --git a/002_source/cms/src/components/dashboard/error/index.tsx b/002_source/cms/src/components/dashboard/error/index.tsx index 9c13eb1..46ba2e0 100644 --- a/002_source/cms/src/components/dashboard/error/index.tsx +++ b/002_source/cms/src/components/dashboard/error/index.tsx @@ -39,9 +39,11 @@ function ErrorDetails({ details }: { details: string }): React.JSX.Element { - - {details} - +
+            
+              {details}
+            
+          
diff --git a/002_source/cms/src/components/dashboard/lesson_type/lesson-types-filters.tsx b/002_source/cms/src/components/dashboard/lesson_type/lesson-types-filters.tsx index 794aa46..1780fb3 100644 --- a/002_source/cms/src/components/dashboard/lesson_type/lesson-types-filters.tsx +++ b/002_source/cms/src/components/dashboard/lesson_type/lesson-types-filters.tsx @@ -24,9 +24,8 @@ import { pb } from '@/lib/pb'; import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button'; import { Option } from '@/components/core/option'; -// import type { LessonType } from './ILessonType'; import { useLessonTypesSelection } from './lesson-types-selection-context'; -import { LessonType } from './types'; +import type { LessonType } from './types'; export interface Filters { email?: string; @@ -61,18 +60,6 @@ export function LessonTypesFilters({ const selection = useLessonTypesSelection(); - function getVisible(): number { - return fullData.reduce((count, item: LessonType) => { - return item.visible === 'visible' ? count + 1 : count; - }, 0); - } - - function getHidden(): number { - return fullData.reduce((count, item: LessonType) => { - return item.visible === 'hidden' ? count + 1 : count; - }, 0); - } - // The tabs should be generated using API data. const tabs = [ { label: t('All'), value: '', count: totalCount }, @@ -195,7 +182,13 @@ export function LessonTypesFilters({ return (
- + {tabs.map((tab) => ( } diff --git a/002_source/cms/src/components/dashboard/lp_categories/_constants.ts b/002_source/cms/src/components/dashboard/lp_categories/_constants.ts new file mode 100644 index 0000000..5e48669 --- /dev/null +++ b/002_source/cms/src/components/dashboard/lp_categories/_constants.ts @@ -0,0 +1,41 @@ +import { NO_NUM, NO_VALUE } from '@/constants'; +import type { RecordModel } from 'pocketbase'; + +import { CreateForm, LpCategory } from '@/types/LpCategory'; +import { dayjs } from '@/lib/dayjs'; + +// import type { CreateForm, LpCategory } from './types'; + +export const LpCategoryCreateFormDefault: CreateForm = { + name: '', + description: '', + isActive: true, + order: 1, + imageUrl: '', +}; + +export const defaultLpCategory: LpCategory = { + id: '', + name: '', + description: '', + isActive: true, + order: 1, + imageUrl: '', + createdAt: dayjs().toDate(), + updatedAt: dayjs().toDate(), + visible: 'active', + status: '', +}; + +export const emptyLpCategory: LpCategory = { + ...defaultLpCategory, + isActive: false, +}; + +export const LpCategoryDefaultValue = { + createForm: LpCategoryCreateFormDefault, + default: defaultLpCategory, + empty: emptyLpCategory, +}; + +export default LpCategoryDefaultValue; diff --git a/002_source/cms/src/components/dashboard/lp_categories/lp-categories-filters.tsx b/002_source/cms/src/components/dashboard/lp_categories/lp-categories-filters.tsx index f1add2a..9083e2f 100644 --- a/002_source/cms/src/components/dashboard/lp_categories/lp-categories-filters.tsx +++ b/002_source/cms/src/components/dashboard/lp_categories/lp-categories-filters.tsx @@ -3,6 +3,8 @@ import * as React from 'react'; import { useRouter } from 'next/navigation'; 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'; @@ -16,41 +18,56 @@ import Tabs from '@mui/material/Tabs'; import Typography from '@mui/material/Typography'; import { useTranslation } from 'react-i18next'; +import type { LpCategory } from '@/types/LpCategory'; import { paths } from '@/paths'; import { FilterButton, FilterPopover, useFilterContext } from '@/components/core/filter-button'; import { Option } from '@/components/core/option'; import { useLpCategoriesSelection } from './lp-categories-selection-context'; -// The tabs should be generated using API data. -const tabs1 = [ - { label: 'All', value: '', count: 5 }, - { label: 'Active', value: 'active', count: 3 }, - { label: 'Pending', value: 'pending', count: 1 }, - { label: 'Blocked', value: 'blocked', count: 1 }, -] as const; - export interface Filters { email?: string; phone?: string; status?: string; + name?: string; + visible?: string; + type?: string; } export type SortDir = 'asc' | 'desc'; -export interface CustomersFiltersProps { +export interface LpCategoriesFiltersProps { filters?: Filters; sortDir?: SortDir; + fullData: LpCategory[]; } -export function LpCategoriesFilters({ filters = {}, sortDir = 'desc' }: CustomersFiltersProps): React.JSX.Element { +export function LpCategoriesFilters({ + filters = {}, + sortDir = 'desc', + fullData, +}: LpCategoriesFiltersProps): React.JSX.Element { const { t } = useTranslation(); - const { email, phone, status } = filters; + 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(); + // 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(); @@ -71,6 +88,18 @@ export function LpCategoriesFilters({ filters = {}, sortDir = 'desc' }: Customer 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); + } + router.push(`${paths.dashboard.lp_categories.list}?${searchParams.toString()}`); }, [router] @@ -87,6 +116,27 @@ export function LpCategoriesFilters({ filters = {}, sortDir = 'desc' }: Customer [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); @@ -108,28 +158,35 @@ export function LpCategoriesFilters({ filters = {}, sortDir = 'desc' }: Customer [updateSearchParams, filters] ); - const [allCount, setAllCount] = React.useState(0); - React.useEffect(() => { - async function fetchAllCount(): Promise { - setAllCount(await GetAllCount()); - } + const fetchCount = async (): Promise => { + try { + const tc = await GetAllCount(); + setTotalCount(tc); - void fetchAllCount(); + const vc = await GetVisibleCount(); + setVisibleCount(vc); + + const hc = await GetHiddenCount(); + setHiddenCount(hc); + } catch (error) { + // + } + }; + void fetchCount(); }, []); - const tabs = [ - { label: 'All', value: '', count: allCount }, - { label: 'Active', value: 'active', count: 3 }, - { label: 'Pending', value: 'pending', count: 1 }, - { label: 'Blocked', value: 'blocked', count: 1 }, - ] as const; - - const hasFilters = status || email || phone; + const hasFilters = status || email || phone || visible || name || type; return (
- + {tabs.map((tab) => ( } @@ -146,35 +203,37 @@ export function LpCategoriesFilters({ filters = {}, sortDir = 'desc' }: Customer { - handleEmailChange(value as string); + handleNameChange(value as string); }} onFilterDelete={() => { - handleEmailChange(); + handleNameChange(); }} - popover={} - value={email} + popover={} + value={name} /> + { - handlePhoneChange(value as string); + handleTypeChange(value as string); }} onFilterDelete={() => { - handlePhoneChange(); + handleTypeChange(); }} - popover={} - value={phone} + popover={} + value={type} /> + {hasFilters ? : null} {selection.selectedAny ? ( - {selection.selected.size} selected + {selection.selected.size} {t('selected')} + + ); +} + +function EmailFilterPopover(): React.JSX.Element { + 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 PhoneFilterPopover(): React.JSX.Element { - const { t } = useTranslation(); const { anchorEl, onApply, onClose, open, value: initialValue } = useFilterContext(); const [value, setValue] = React.useState(''); @@ -236,7 +365,7 @@ function PhoneFilterPopover(): React.JSX.Element { }, [initialValue]); return ( - + { @@ -248,7 +377,6 @@ function PhoneFilterPopover(): React.JSX.Element { } }} value={value} - placeholder={t('Enter phone number')} /> ); diff --git a/002_source/cms/src/components/dashboard/lp_categories/lp-categories-pagination.tsx b/002_source/cms/src/components/dashboard/lp_categories/lp-categories-pagination.tsx index 48c4ba3..c8268bc 100644 --- a/002_source/cms/src/components/dashboard/lp_categories/lp-categories-pagination.tsx +++ b/002_source/cms/src/components/dashboard/lp_categories/lp-categories-pagination.tsx @@ -7,25 +7,42 @@ function noop(): void { return undefined; } -interface CustomersPaginationProps { +interface LpCategoriesPaginationProps { count: number; page: number; + setPage: (page: number) => void; + setRowsPerPage: (page: number) => void; + rowsPerPage: number; } -export function LpCategoriesPagination({ count, page }: CustomersPaginationProps): React.JSX.Element { +export function LpCategoriesPagination({ + count, + page, + setPage, + setRowsPerPage, + rowsPerPage, +}: LpCategoriesPaginationProps): 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/lp_categories/lp-categories-table.tsx b/002_source/cms/src/components/dashboard/lp_categories/lp-categories-table.tsx index ecefa51..448fd83 100644 --- a/002_source/cms/src/components/dashboard/lp_categories/lp-categories-table.tsx +++ b/002_source/cms/src/components/dashboard/lp_categories/lp-categories-table.tsx @@ -29,77 +29,6 @@ import type { ColumnDef } from '@/components/core/data-table'; import type { LessonCategory } from '../lesson_category/types'; import { useLpCategoriesSelection } from './lp-categories-selection-context'; -const columns1 = [ - { - formatter: (row): React.JSX.Element => ( - - {' '} -
- - {row.name} - - - {row.email} - -
-
- ), - name: 'Name', - width: '250px', - }, - { - formatter: (row): React.JSX.Element => ( - - - - {new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 2 }).format(row.quota / 100)} - - - ), - name: 'Quota', - width: '250px', - }, - { field: 'phone', name: 'Phone number', width: '150px' }, - { - formatter(row) { - return dayjs(row.createdAt).format('MMM D, YYYY h:mm A'); - }, - name: 'Created at', - width: '200px', - }, - { - formatter: (row): React.JSX.Element => { - const mapping = { - active: { label: 'Active', icon: }, - blocked: { label: 'Blocked', icon: }, - pending: { label: 'Pending', icon: }, - } as const; - const { label, icon } = mapping[row.status] ?? { label: 'Unknown', icon: null }; - - return ; - }, - name: 'Status', - width: '150px', - }, - { - formatter: (): React.JSX.Element => ( - - - - ), - name: 'Actions', - hideName: true, - width: '100px', - align: 'right', - }, -] satisfies ColumnDef[]; - function columns(handleDeleteClick: (testId: string) => void): ColumnDef[] { return [ { @@ -134,7 +63,9 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef - {new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 2 }).format(row.quota / 100)} + {new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 2 }).format( + row?.quota ? row.quota / 100 : 0 + )}
), @@ -153,7 +84,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef }, NA: { label: 'NA', icon: }, } as const; - const { label, icon } = mapping[row.status] ?? { label: 'Unknown', icon: null }; + const { label, icon } = mapping[row.visible] ?? { label: 'Unknown', icon: null }; return (