update,
This commit is contained in:
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import GetAllCount from '@/db/LessonCategories/GetAllCount';
|
import GetAllCount from '@/db/Vocabularies/GetAllCount';
|
||||||
import GetHiddenCount from '@/db/LessonCategories/GetHiddenCount';
|
import GetHiddenCount from '@/db/Vocabularies/GetHiddenCount';
|
||||||
import GetVisibleCount from '@/db/LessonCategories/GetVisibleCount';
|
import GetVisibleCount from '@/db/Vocabularies/GetVisibleCount';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
|
@@ -5,8 +5,7 @@ import * as React from 'react';
|
|||||||
import { useSelection } from '@/hooks/use-selection';
|
import { useSelection } from '@/hooks/use-selection';
|
||||||
import type { Selection } from '@/hooks/use-selection';
|
import type { Selection } from '@/hooks/use-selection';
|
||||||
|
|
||||||
import { Vocabulary } from './type';
|
import type { Vocabulary } from './type';
|
||||||
|
|
||||||
|
|
||||||
function noop(): void {
|
function noop(): void {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@@ -5,21 +5,14 @@ import RouterLink from 'next/link';
|
|||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
|
||||||
import Chip from '@mui/material/Chip';
|
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 Link from '@mui/material/Link';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import Typography from '@mui/material/Typography';
|
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 { 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 { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr/PencilSimple';
|
||||||
import { TrashSimple as TrashSimpleIcon } from '@phosphor-icons/react/dist/ssr/TrashSimple';
|
import { TrashSimple as TrashSimpleIcon } from '@phosphor-icons/react/dist/ssr/TrashSimple';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
@@ -29,16 +22,8 @@ import type { ColumnDef } from '@/components/core/data-table';
|
|||||||
import ConfirmDeleteModal from './confirm-delete-modal';
|
import ConfirmDeleteModal from './confirm-delete-modal';
|
||||||
import { useVocabulariesSelection } from './vocabularies-selection-context';
|
import { useVocabulariesSelection } from './vocabularies-selection-context';
|
||||||
import type { Vocabulary } from './type';
|
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(
|
function columns(handleDeleteClick: (testId: string) => void): ColumnDef<Vocabulary>[] {
|
||||||
handleDeleteClick: (testId: string) => void,
|
|
||||||
lessonCategories: { id: string; label: string }[]
|
|
||||||
): ColumnDef<Vocabulary>[] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
formatter: (row): React.JSX.Element => (
|
formatter: (row): React.JSX.Element => (
|
||||||
@@ -173,20 +158,6 @@ export function VocabulariesTable({ rows, reloadRows }: VocabulariesTableProps):
|
|||||||
setIdToDelete(testId);
|
setIdToDelete(testId);
|
||||||
}
|
}
|
||||||
|
|
||||||
let [lessonCategories, setLessonCategories] = React.useState<{}>({});
|
|
||||||
|
|
||||||
async function tempFunc(): Promise<void> {
|
|
||||||
try {
|
|
||||||
const tempCategories = await listLessonCategories();
|
|
||||||
console.log(tempCategories);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
React.useEffect(() => {
|
|
||||||
void tempFunc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ConfirmDeleteModal
|
<ConfirmDeleteModal
|
||||||
|
@@ -2,75 +2,85 @@
|
|||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import RouterLink from 'next/link';
|
import RouterLink from 'next/link';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { COL_VOCABULARIES } from '@/constants';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
import { Avatar, Divider } from '@mui/material';
|
import { Avatar, Divider, MenuItem } from '@mui/material';
|
||||||
// import Avatar from '@mui/material/Avatar';
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Card from '@mui/material/Card';
|
import Card from '@mui/material/Card';
|
||||||
import CardActions from '@mui/material/CardActions';
|
import CardActions from '@mui/material/CardActions';
|
||||||
import CardContent from '@mui/material/CardContent';
|
import CardContent from '@mui/material/CardContent';
|
||||||
|
|
||||||
import FormControl from '@mui/material/FormControl';
|
import FormControl from '@mui/material/FormControl';
|
||||||
import FormHelperText from '@mui/material/FormHelperText';
|
import FormHelperText from '@mui/material/FormHelperText';
|
||||||
import InputLabel from '@mui/material/InputLabel';
|
import InputLabel from '@mui/material/InputLabel';
|
||||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||||
|
import Select from '@mui/material/Select';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Grid from '@mui/material/Unstable_Grid2';
|
import Grid from '@mui/material/Unstable_Grid2';
|
||||||
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
|
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
|
||||||
// import axios from 'axios';
|
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z as zod } from 'zod';
|
import { z as zod } from 'zod';
|
||||||
|
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
import { logger } from '@/lib/default-logger';
|
import { logger } from '@/lib/default-logger';
|
||||||
import { fileToBase64 } from '@/lib/file-to-base64';
|
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 { TextEditor } from '@/components/core/text-editor/text-editor';
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
|
import ErrorDisplay from '../error';
|
||||||
|
import type { EditFormProps } from './type';
|
||||||
|
import { listLessonCategories } from '@/db/LessonCategories/listLessonCategories';
|
||||||
|
import isDevelopment from '@/lib/check-is-development';
|
||||||
|
|
||||||
const schema = zod.object({
|
const schema = zod.object({
|
||||||
|
image: zod.union([zod.array(zod.any()), zod.string()]).optional(),
|
||||||
|
sound: zod.union([zod.array(zod.any()), zod.string()]).optional(),
|
||||||
|
word: zod.string().min(1, 'Word is required').max(255),
|
||||||
|
word_c: zod.string().min(1, 'Chinese word is required').max(255),
|
||||||
|
sample_e: zod.string().optional(),
|
||||||
|
sample_c: zod.string().optional(),
|
||||||
|
cat_id: zod.string().min(1, 'Category ID is required'),
|
||||||
|
category: zod.string().optional(),
|
||||||
|
lesson_type_id: zod.string().optional(),
|
||||||
|
// NOTE: for image handling
|
||||||
avatar: zod.string().optional(),
|
avatar: zod.string().optional(),
|
||||||
name: zod.string().min(1, 'Name is required').max(255),
|
visible: zod.string(),
|
||||||
email: zod.string().email('Must be a valid email').min(1, 'Email is required').max(255),
|
|
||||||
phone: zod.string().min(1, 'Phone is required').max(15),
|
|
||||||
company: zod.string().max(255),
|
|
||||||
billingAddress: zod.object({
|
|
||||||
country: zod.string().min(1, 'Country is required').max(255),
|
|
||||||
state: zod.string().min(1, 'State is required').max(255),
|
|
||||||
city: zod.string().min(1, 'City is required').max(255),
|
|
||||||
zipCode: zod.string().min(1, 'Zip code is required').max(255),
|
|
||||||
line1: zod.string().min(1, 'Street line 1 is required').max(255),
|
|
||||||
line2: zod.string().max(255).optional(),
|
|
||||||
}),
|
|
||||||
taxId: zod.string().max(255).optional(),
|
|
||||||
timezone: zod.string().min(1, 'Timezone is required').max(255),
|
|
||||||
language: zod.string().min(1, 'Language is required').max(255),
|
|
||||||
currency: zod.string().min(1, 'Currency is required').max(255),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type Values = zod.infer<typeof schema>;
|
type Values = zod.infer<typeof schema>;
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
avatar: '',
|
image: undefined,
|
||||||
name: '',
|
sound: undefined,
|
||||||
email: '',
|
word: '',
|
||||||
phone: '',
|
word_c: '',
|
||||||
company: '',
|
sample_e: '',
|
||||||
billingAddress: { country: '', state: '', city: '', zipCode: '', line1: '', line2: '' },
|
sample_c: '',
|
||||||
taxId: '',
|
cat_id: '',
|
||||||
timezone: 'new_york',
|
category: '',
|
||||||
language: 'en',
|
lesson_type_id: '',
|
||||||
currency: 'USD',
|
visible: 'visible',
|
||||||
} satisfies Values;
|
} satisfies Values;
|
||||||
|
|
||||||
export function VocabularyCreateForm(): React.JSX.Element {
|
export function VocabularyCreateForm(): React.JSX.Element {
|
||||||
const router = useRouter();
|
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<boolean>(false);
|
const [isCreating, setIsCreating] = React.useState<boolean>(false);
|
||||||
|
const [showLoading, setShowLoading] = React.useState<boolean>(false);
|
||||||
|
//
|
||||||
|
const [showError, setShowError] = React.useState({ show: false, detail: '' });
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@@ -83,15 +93,33 @@ export function VocabularyCreateForm(): React.JSX.Element {
|
|||||||
const onSubmit = React.useCallback(
|
const onSubmit = React.useCallback(
|
||||||
async (values: Values): Promise<void> => {
|
async (values: Values): Promise<void> => {
|
||||||
setIsCreating(true);
|
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 {
|
try {
|
||||||
// Make API request
|
const result = await pb.collection(COL_VOCABULARIES).create(tempUpdate);
|
||||||
toast.success('Customer updated');
|
logger.debug(result);
|
||||||
router.push(paths.dashboard.lesson_categories.details('1'));
|
toast.success(t('create.success'));
|
||||||
|
// router.push(paths.dashboard.lesson_categories.details('1'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(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]
|
[router]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -110,6 +138,43 @@ export function VocabularyCreateForm(): React.JSX.Element {
|
|||||||
[setValue]
|
[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 <FormLoading />;
|
||||||
|
if (showError.show)
|
||||||
|
return (
|
||||||
|
<ErrorDisplay
|
||||||
|
message={t('error.unable-to-process-request')}
|
||||||
|
code="500"
|
||||||
|
details={showError.detail}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Card>
|
<Card>
|
||||||
@@ -183,84 +248,125 @@ export function VocabularyCreateForm(): React.JSX.Element {
|
|||||||
xs={12}
|
xs={12}
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
|
disabled={isCreating}
|
||||||
control={control}
|
control={control}
|
||||||
name="name"
|
name="word"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
error={Boolean(errors.name)}
|
disabled={isCreating}
|
||||||
|
error={Boolean(errors.word)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>{t('create.name')}</InputLabel>
|
<InputLabel required>{t('create.word')}</InputLabel>
|
||||||
<OutlinedInput {...field} />
|
<OutlinedInput {...field} />
|
||||||
{errors.name ? <FormHelperText>{errors.name.message}</FormHelperText> : null}
|
{errors.word ? <FormHelperText>{errors.word.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{/* */}
|
||||||
|
<Grid
|
||||||
|
md={6}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
disabled={isCreating}
|
||||||
|
control={control}
|
||||||
|
name="word_c"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.word_c)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<InputLabel required>{t('create.word_c')}</InputLabel>
|
||||||
|
<OutlinedInput {...field} />
|
||||||
|
{errors.word_c ? <FormHelperText>{errors.word_c.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
{/* */}
|
||||||
|
<Grid
|
||||||
|
md={6}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
disabled={isCreating}
|
||||||
|
control={control}
|
||||||
|
name="cat_id"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.cat_id)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<InputLabel>{t('create.cat_id')}</InputLabel>
|
||||||
|
<Select {...field}>
|
||||||
|
{categoriesOption.map((co, i) => (
|
||||||
|
<Option
|
||||||
|
key={i}
|
||||||
|
value={co.value}
|
||||||
|
>
|
||||||
|
{co.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
{/* */}
|
||||||
|
<Grid
|
||||||
|
md={6}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
disabled={isCreating}
|
||||||
|
control={control}
|
||||||
|
name="sound"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.sound)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{/* TODO: sound file selection is not implemented */}
|
||||||
|
<InputLabel required>{t('create.sound_file')} - (Not implemented)</InputLabel>
|
||||||
|
<OutlinedInput {...field} />
|
||||||
|
{errors.sound ? <FormHelperText>{errors.sound.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
md={6}
|
md={6}
|
||||||
xs={12}
|
xs={12}
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="email"
|
name="visible"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
error={Boolean(errors.email)}
|
error={Boolean(errors.visible)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>Email address</InputLabel>
|
<InputLabel>{t('create.visible')}</InputLabel>
|
||||||
<OutlinedInput
|
<Select {...field}>
|
||||||
{...field}
|
<MenuItem value="visible">visible</MenuItem>
|
||||||
type="email"
|
<MenuItem value="hidden">hidden</MenuItem>
|
||||||
/>
|
</Select>
|
||||||
{errors.email ? <FormHelperText>{errors.email.message}</FormHelperText> : null}
|
|
||||||
</FormControl>
|
{errors.visible ? <FormHelperText>{errors.visible.message}</FormHelperText> : null}
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid
|
|
||||||
md={6}
|
|
||||||
xs={12}
|
|
||||||
>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="phone"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControl
|
|
||||||
error={Boolean(errors.phone)}
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
<InputLabel required>Phone number</InputLabel>
|
|
||||||
<OutlinedInput {...field} />
|
|
||||||
{errors.phone ? <FormHelperText>{errors.phone.message}</FormHelperText> : null}
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid
|
|
||||||
md={6}
|
|
||||||
xs={12}
|
|
||||||
>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="company"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControl
|
|
||||||
error={Boolean(errors.company)}
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
<InputLabel>Company</InputLabel>
|
|
||||||
<OutlinedInput {...field} />
|
|
||||||
{errors.company ? <FormHelperText>{errors.company.message}</FormHelperText> : null}
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
{/* */}
|
||||||
|
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Typography variant="h6">{t('create.detail-information')}</Typography>
|
<Typography variant="h6">{t('create.sample-sentence')}</Typography>
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
spacing={3}
|
spacing={3}
|
||||||
@@ -270,23 +376,22 @@ export function VocabularyCreateForm(): React.JSX.Element {
|
|||||||
xs={12}
|
xs={12}
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
|
disabled={isCreating}
|
||||||
control={control}
|
control={control}
|
||||||
name="billingAddress.country"
|
name="sample_e"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Box>
|
<FormControl
|
||||||
<Typography
|
error={Boolean(errors.sample_e)}
|
||||||
variant="subtitle1"
|
fullWidth
|
||||||
color="text-secondary"
|
>
|
||||||
>
|
<InputLabel>{t('create.sample_e')}</InputLabel>
|
||||||
{t('create.description')}
|
<OutlinedInput
|
||||||
</Typography>
|
{...field}
|
||||||
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
|
multiline
|
||||||
<TextEditor
|
rows={4}
|
||||||
content=""
|
/>
|
||||||
placeholder="Write something"
|
{errors.sample_e ? <FormHelperText>{errors.sample_e.message}</FormHelperText> : null}
|
||||||
/>
|
</FormControl>
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -295,35 +400,35 @@ export function VocabularyCreateForm(): React.JSX.Element {
|
|||||||
xs={12}
|
xs={12}
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
|
disabled={isCreating}
|
||||||
control={control}
|
control={control}
|
||||||
name="billingAddress.state"
|
name="sample_c"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Box>
|
<FormControl
|
||||||
<Typography
|
error={Boolean(errors.sample_c)}
|
||||||
variant="subtitle1"
|
fullWidth
|
||||||
color="text.secondary"
|
>
|
||||||
>
|
<InputLabel>{t('create.sample_c')}</InputLabel>
|
||||||
{t('create.remarks')}
|
<OutlinedInput
|
||||||
</Typography>
|
{...field}
|
||||||
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
|
multiline
|
||||||
<TextEditor
|
rows={4}
|
||||||
content=""
|
/>
|
||||||
placeholder="Write something"
|
{errors.sample_c ? <FormHelperText>{errors.sample_c.message}</FormHelperText> : null}
|
||||||
/>
|
</FormControl>
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
{/* */}
|
||||||
</Stack>
|
</Stack>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
href={paths.dashboard.lesson_categories.list}
|
href={paths.dashboard.vocabularies.list}
|
||||||
>
|
>
|
||||||
{t('create.cancelButton')}
|
{t('create.cancelButton')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -337,6 +442,9 @@ export function VocabularyCreateForm(): React.JSX.Element {
|
|||||||
{t('create.createButton')}
|
{t('create.createButton')}
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
|
<Box sx={{ display: isDevelopment ? 'block' : 'none' }}>
|
||||||
|
<pre>{JSON.stringify(errors, null, 2)}</pre>
|
||||||
|
</Box>
|
||||||
</Card>
|
</Card>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
@@ -6,8 +6,7 @@ import { useParams, useRouter } from 'next/navigation';
|
|||||||
import { COL_VOCABULARIES } from '@/constants';
|
import { COL_VOCABULARIES } from '@/constants';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
import { Avatar, Divider, MenuItem } from '@mui/material';
|
import { Avatar, Divider } from '@mui/material';
|
||||||
// import Avatar from '@mui/material/Avatar';
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Card from '@mui/material/Card';
|
import Card from '@mui/material/Card';
|
||||||
@@ -23,26 +22,20 @@ import Stack from '@mui/material/Stack';
|
|||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Grid from '@mui/material/Unstable_Grid2';
|
import Grid from '@mui/material/Unstable_Grid2';
|
||||||
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
|
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
|
||||||
import type { RecordModel } from 'pocketbase';
|
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z as zod } from 'zod';
|
import { z as zod } from 'zod';
|
||||||
|
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
import { dayjs } from '@/lib/dayjs';
|
|
||||||
import { logger } from '@/lib/default-logger';
|
import { logger } from '@/lib/default-logger';
|
||||||
import { base64ToFile, fileToBase64 } from '@/lib/file-to-base64';
|
import { base64ToFile, fileToBase64 } from '@/lib/file-to-base64';
|
||||||
import { pb } from '@/lib/pb';
|
import { pb } from '@/lib/pb';
|
||||||
import { Option } from '@/components/core/option';
|
import { Option } from '@/components/core/option';
|
||||||
import { TextEditor } from '@/components/core/text-editor/text-editor';
|
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
import FormLoading from '@/components/loading';
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
import ErrorDisplay from '../error';
|
import ErrorDisplay from '../error';
|
||||||
import { defaultLessonCategory } from './_constants';
|
|
||||||
import type { EditFormProps } from './type';
|
import type { EditFormProps } from './type';
|
||||||
import getVocabularyById from '@/db/Vocabularies/GetById';
|
|
||||||
import getAllLessonCategories from '@/db/LessonCategories/GetAll';
|
|
||||||
import { listLessonCategories } from '@/db/LessonCategories/listLessonCategories';
|
import { listLessonCategories } from '@/db/LessonCategories/listLessonCategories';
|
||||||
import isDevelopment from '@/lib/check-is-development';
|
import isDevelopment from '@/lib/check-is-development';
|
||||||
|
|
||||||
@@ -55,7 +48,7 @@ const schema = zod.object({
|
|||||||
sample_c: zod.string().optional(),
|
sample_c: zod.string().optional(),
|
||||||
cat_id: zod.string().min(1, 'Category ID is required'),
|
cat_id: zod.string().min(1, 'Category ID is required'),
|
||||||
category: zod.string().optional(),
|
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
|
// NOTE: for image handling
|
||||||
avatar: zod.string().optional(),
|
avatar: zod.string().optional(),
|
||||||
});
|
});
|
||||||
@@ -111,7 +104,6 @@ export function VocabularyEditForm(): React.JSX.Element {
|
|||||||
};
|
};
|
||||||
//
|
//
|
||||||
try {
|
try {
|
||||||
console.log({ tempUpdate });
|
|
||||||
const result = await pb.collection(COL_VOCABULARIES).update(catId, tempUpdate);
|
const result = await pb.collection(COL_VOCABULARIES).update(catId, tempUpdate);
|
||||||
logger.debug(result);
|
logger.debug(result);
|
||||||
toast.success(t('edit.success'));
|
toast.success(t('edit.success'));
|
||||||
@@ -173,7 +165,7 @@ export function VocabularyEditForm(): React.JSX.Element {
|
|||||||
[catId]
|
[catId]
|
||||||
);
|
);
|
||||||
|
|
||||||
let [categoriesOption, setCategoriesOption] = React.useState<{ value: string; label: string }[]>([]);
|
const [categoriesOption, setCategoriesOption] = React.useState<{ value: string; label: string }[]>([]);
|
||||||
const loadCategories = React.useCallback(async () => {
|
const loadCategories = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const categories = await listLessonCategories();
|
const categories = await listLessonCategories();
|
||||||
@@ -193,10 +185,8 @@ export function VocabularyEditForm(): React.JSX.Element {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setShowLoading(true);
|
setShowLoading(true);
|
||||||
|
|
||||||
void loadCategories();
|
void loadCategories();
|
||||||
void loadExistingData(catId);
|
void loadExistingData(catId);
|
||||||
|
|
||||||
setShowLoading(false);
|
setShowLoading(false);
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
Reference in New Issue
Block a user