diff --git a/002_source/cms/src/components/dashboard/vocabulary/vocabularies-filters.tsx b/002_source/cms/src/components/dashboard/vocabulary/vocabularies-filters.tsx index 2ef4f61..8641090 100644 --- a/002_source/cms/src/components/dashboard/vocabulary/vocabularies-filters.tsx +++ b/002_source/cms/src/components/dashboard/vocabulary/vocabularies-filters.tsx @@ -2,9 +2,9 @@ import * as React from 'react'; import { useRouter } from 'next/navigation'; -import GetAllCount from '@/db/LessonCategories/GetAllCount'; -import GetHiddenCount from '@/db/LessonCategories/GetHiddenCount'; -import GetVisibleCount from '@/db/LessonCategories/GetVisibleCount'; +import GetAllCount from '@/db/Vocabularies/GetAllCount'; +import GetHiddenCount from '@/db/Vocabularies/GetHiddenCount'; +import GetVisibleCount from '@/db/Vocabularies/GetVisibleCount'; import Button from '@mui/material/Button'; import Chip from '@mui/material/Chip'; import Divider from '@mui/material/Divider'; diff --git a/002_source/cms/src/components/dashboard/vocabulary/vocabularies-selection-context.tsx b/002_source/cms/src/components/dashboard/vocabulary/vocabularies-selection-context.tsx index ef1ddc3..fd1b6c9 100644 --- a/002_source/cms/src/components/dashboard/vocabulary/vocabularies-selection-context.tsx +++ b/002_source/cms/src/components/dashboard/vocabulary/vocabularies-selection-context.tsx @@ -5,8 +5,7 @@ import * as React from 'react'; import { useSelection } from '@/hooks/use-selection'; import type { Selection } from '@/hooks/use-selection'; -import { Vocabulary } from './type'; - +import type { Vocabulary } from './type'; function noop(): void { return undefined; diff --git a/002_source/cms/src/components/dashboard/vocabulary/vocabularies-table.tsx b/002_source/cms/src/components/dashboard/vocabulary/vocabularies-table.tsx index d393bc5..44ac46d 100644 --- a/002_source/cms/src/components/dashboard/vocabulary/vocabularies-table.tsx +++ b/002_source/cms/src/components/dashboard/vocabulary/vocabularies-table.tsx @@ -5,21 +5,14 @@ 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 Chip from '@mui/material/Chip'; -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 { 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'; @@ -29,16 +22,8 @@ import type { ColumnDef } from '@/components/core/data-table'; import ConfirmDeleteModal from './confirm-delete-modal'; import { useVocabulariesSelection } from './vocabularies-selection-context'; import type { Vocabulary } from './type'; -import { listLessonCategories } from '@/db/LessonCategories/listLessonCategories'; -import { LessonCategory } from '@/db/LessonCategories/type'; -import { Logger } from '@/lib/logger'; -import { logger } from '@/lib/default-logger'; -import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts'; -function columns( - handleDeleteClick: (testId: string) => void, - lessonCategories: { id: string; label: string }[] -): ColumnDef[] { +function columns(handleDeleteClick: (testId: string) => void): ColumnDef[] { return [ { formatter: (row): React.JSX.Element => ( @@ -173,20 +158,6 @@ export function VocabulariesTable({ rows, reloadRows }: VocabulariesTableProps): setIdToDelete(testId); } - let [lessonCategories, setLessonCategories] = React.useState<{}>({}); - - async function tempFunc(): Promise { - try { - const tempCategories = await listLessonCategories(); - console.log(tempCategories); - } catch (error) { - logger.error(error); - } - } - React.useEffect(() => { - void tempFunc; - }, []); - return ( ; const defaultValues = { - avatar: '', - name: '', - email: '', - phone: '', - company: '', - billingAddress: { country: '', state: '', city: '', zipCode: '', line1: '', line2: '' }, - taxId: '', - timezone: 'new_york', - language: 'en', - currency: 'USD', + image: undefined, + sound: undefined, + word: '', + word_c: '', + sample_e: '', + sample_c: '', + cat_id: '', + category: '', + lesson_type_id: '', + visible: 'visible', } satisfies Values; export function VocabularyCreateForm(): React.JSX.Element { const router = useRouter(); - const { t } = useTranslation(['common', 'lesson_category']); + const { t } = useTranslation(); + + const { cat_id: catId } = useParams<{ cat_id: string }>(); const [isCreating, setIsCreating] = React.useState(false); + const [showLoading, setShowLoading] = React.useState(false); + // + const [showError, setShowError] = React.useState({ show: false, detail: '' }); const { control, @@ -83,15 +93,33 @@ export function VocabularyCreateForm(): React.JSX.Element { const onSubmit = React.useCallback( async (values: Values): Promise => { setIsCreating(true); + + const tempUpdate: EditFormProps = { + image: values.avatar ? [await base64ToFile(values.avatar)] : null, + sound: '', + word: values.word, + word_c: values.word_c, + sample_e: values.sample_e || '', + sample_c: values.sample_c || '', + cat_id: values.cat_id, + category: '', + lesson_type_id: '', + }; + try { - // Make API request - toast.success('Customer updated'); - router.push(paths.dashboard.lesson_categories.details('1')); + const result = await pb.collection(COL_VOCABULARIES).create(tempUpdate); + logger.debug(result); + toast.success(t('create.success')); + // router.push(paths.dashboard.lesson_categories.details('1')); } catch (err) { logger.error(err); - toast.error('Something went wrong!'); + toast.error(t('create.failed')); + } finally { + setIsCreating(false); } }, + // t is not necessary here + // eslint-disable-next-line react-hooks/exhaustive-deps [router] ); @@ -110,6 +138,43 @@ export function VocabularyCreateForm(): React.JSX.Element { [setValue] ); + const [categoriesOption, setCategoriesOption] = React.useState<{ value: string; label: string }[]>([]); + const loadCategories = React.useCallback(async () => { + try { + const categories = await listLessonCategories(); + + logger.debug(categories); + + setCategoriesOption( + categories.map((c) => { + return { label: c.cat_name || '??', value: c.id }; + }) + ); + } catch (error) { + logger.error(error); + toast.error(t('list.error')); + } + }, [catId]); + + React.useEffect(() => { + setShowLoading(true); + void loadCategories(); + // + setShowLoading(false); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [catId]); + + if (showLoading) return ; + if (showError.show) + return ( + + ); + return (
@@ -183,84 +248,125 @@ export function VocabularyCreateForm(): React.JSX.Element { xs={12} > ( - {t('create.name')} + {t('create.word')} - {errors.name ? {errors.name.message} : null} + {errors.word ? {errors.word.message} : null} )} /> + {/* */} + + ( + + {t('create.word_c')} + + {errors.word_c ? {errors.word_c.message} : null} + + )} + /> + + {/* */} + + ( + + {t('create.cat_id')} + + + )} + /> + + {/* */} + + ( + + {/* TODO: sound file selection is not implemented */} + {t('create.sound_file')} - (Not implemented) + + {errors.sound ? {errors.sound.message} : null} + + )} + /> + + ( - Email address - - {errors.email ? {errors.email.message} : null} - - )} - /> - - - ( - - Phone number - - {errors.phone ? {errors.phone.message} : null} - - )} - /> - - - ( - - Company - - {errors.company ? {errors.company.message} : null} + {t('create.visible')} + + + {errors.visible ? {errors.visible.message} : null} )} /> + + {/* */} + - {t('create.detail-information')} + {t('create.sample-sentence')} ( - - - {t('create.description')} - - - - - + + {t('create.sample_e')} + + {errors.sample_e ? {errors.sample_e.message} : null} + )} /> @@ -295,35 +400,35 @@ export function VocabularyCreateForm(): React.JSX.Element { xs={12} > ( - - - {t('create.remarks')} - - - - - + + {t('create.sample_c')} + + {errors.sample_c ? {errors.sample_c.message} : null} + )} /> + {/* */} @@ -337,6 +442,9 @@ export function VocabularyCreateForm(): React.JSX.Element { {t('create.createButton')} + +
{JSON.stringify(errors, null, 2)}
+
); diff --git a/002_source/cms/src/components/dashboard/vocabulary/vocabulary-edit-form.tsx b/002_source/cms/src/components/dashboard/vocabulary/vocabulary-edit-form.tsx index ee2e9b6..5363dc2 100644 --- a/002_source/cms/src/components/dashboard/vocabulary/vocabulary-edit-form.tsx +++ b/002_source/cms/src/components/dashboard/vocabulary/vocabulary-edit-form.tsx @@ -6,8 +6,7 @@ import { useParams, useRouter } from 'next/navigation'; import { COL_VOCABULARIES } from '@/constants'; import { zodResolver } from '@hookform/resolvers/zod'; import { LoadingButton } from '@mui/lab'; -import { Avatar, Divider, MenuItem } from '@mui/material'; -// import Avatar from '@mui/material/Avatar'; +import { Avatar, Divider } from '@mui/material'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Card from '@mui/material/Card'; @@ -23,26 +22,20 @@ 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 type { RecordModel } from 'pocketbase'; import { Controller, useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { z as zod } from 'zod'; import { paths } from '@/paths'; -import { dayjs } from '@/lib/dayjs'; import { logger } from '@/lib/default-logger'; import { base64ToFile, fileToBase64 } from '@/lib/file-to-base64'; import { pb } from '@/lib/pb'; import { Option } from '@/components/core/option'; -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 { defaultLessonCategory } from './_constants'; import type { EditFormProps } from './type'; -import getVocabularyById from '@/db/Vocabularies/GetById'; -import getAllLessonCategories from '@/db/LessonCategories/GetAll'; import { listLessonCategories } from '@/db/LessonCategories/listLessonCategories'; import isDevelopment from '@/lib/check-is-development'; @@ -55,7 +48,7 @@ const schema = zod.object({ sample_c: zod.string().optional(), cat_id: zod.string().min(1, 'Category ID is required'), category: zod.string().optional(), - lesson_type_id: zod.string().min(1, 'Lesson type ID is required'), + lesson_type_id: zod.string().optional(), // NOTE: for image handling avatar: zod.string().optional(), }); @@ -111,7 +104,6 @@ export function VocabularyEditForm(): React.JSX.Element { }; // try { - console.log({ tempUpdate }); const result = await pb.collection(COL_VOCABULARIES).update(catId, tempUpdate); logger.debug(result); toast.success(t('edit.success')); @@ -173,7 +165,7 @@ export function VocabularyEditForm(): React.JSX.Element { [catId] ); - let [categoriesOption, setCategoriesOption] = React.useState<{ value: string; label: string }[]>([]); + const [categoriesOption, setCategoriesOption] = React.useState<{ value: string; label: string }[]>([]); const loadCategories = React.useCallback(async () => { try { const categories = await listLessonCategories(); @@ -193,10 +185,8 @@ export function VocabularyEditForm(): React.JSX.Element { React.useEffect(() => { setShowLoading(true); - void loadCategories(); void loadExistingData(catId); - setShowLoading(false); // eslint-disable-next-line react-hooks/exhaustive-deps