update edit lesson categories,

This commit is contained in:
louiscklaw
2025-04-17 13:26:30 +08:00
parent 6b97358f50
commit ed0d058d04
29 changed files with 1255 additions and 268 deletions

View File

@@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}

View File

@@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}

View File

@@ -38,24 +38,25 @@ 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 { defaultLessonCategory } from '@/components/dashboard/lesson_category/_constants.ts';
// import { defaultLessonCategory } from '@/components/dashboard/lesson_category/defaultLessonCategory';
import { Notifications } from '@/components/dashboard/lesson_category/notifications';
import { Payments } from '@/components/dashboard/lesson_category/payments';
import type { Address } from '@/components/dashboard/lesson_category/shipping-address';
import { ShippingAddress } from '@/components/dashboard/lesson_category/shipping-address';
import type { LessonCategory } from '@/components/dashboard/lesson_category/type.d.ts';
import type { LessonCategory } from '@/components/dashboard/lesson_category/types';
import FormLoading from '@/components/loading';
// export const metadata = { title: `Details | Customers | Dashboard | ${config.site.name}` } satisfies Metadata;
export default function Page(): React.JSX.Element {
const { t } = useTranslation();
const { t } = useTranslation(['common']);
const router = useRouter();
//
const { cat_id: catId } = useParams<{ cat_id: string }>();
//
const [showLoadin, setIsLoading] = React.useState<boolean>(true);
const [showLoading, setShowLoading] = React.useState<boolean>(true);
const [showError, setShowError] = React.useState<boolean>(false);
//
const [showLessonCategory, setShowLessonCategory] = React.useState<LessonCategory>(defaultLessonCategory);
@@ -70,16 +71,29 @@ export default function Page(): React.JSX.Element {
.getOne(catId)
.then((model: RecordModel) => {
setShowLessonCategory({ ...defaultLessonCategory, ...model });
setIsLoading(false);
})
.catch((err) => {
logger.error(err);
toast(t('dashboard.lessonTypes.list.error'));
setShowError(true);
})
.finally(() => {
setShowLoading(false);
});
}
}, [catId]);
if (showLoadin) return <FormLoading />;
if (showLoading) return <FormLoading />;
if (showError)
return (
<ErrorDisplay
message={t('error.unable-to-process-request', { ns: 'common' })}
code="500"
details={t('error.detailed-error-information', { ns: 'common' })}
/>
);
return (
<Box
@@ -106,8 +120,12 @@ export default function Page(): React.JSX.Element {
</div>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}>
<Avatar src="/assets/avatar-1.png" sx={{ '--Avatar-size': '64px' }}>
MV
<Avatar
src={`http://127.0.0.1:8090/api/files/${showLessonCategory.collectionId}/${showLessonCategory.id}/${showLessonCategory.cat_image}`}
sx={{ '--Avatar-size': '64px' }}
variant="rounded"
>
empty
</Avatar>
<div>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>
@@ -150,7 +168,7 @@ export default function Page(): React.JSX.Element {
<UserIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Basic details"
title={t('basic-details', { ns: 'lesson_category' })}
/>
<PropertyList
divider={<Divider />}
@@ -199,7 +217,7 @@ export default function Page(): React.JSX.Element {
<ShieldWarningIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Security"
title={t('security', { ns: 'lesson_category' })}
/>
<CardContent>
<Stack spacing={1}>
@@ -272,7 +290,7 @@ export default function Page(): React.JSX.Element {
<CreditCardIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Billing details"
title={t('billing-details', { ns: 'lesson_category' })}
/>
<CardContent>
<Card sx={{ borderRadius: 1 }} variant="outlined">
@@ -307,7 +325,7 @@ export default function Page(): React.JSX.Element {
<HouseIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Shipping addresses"
title={t('shipping-addresses', { ns: 'lesson_category' })}
/>
<CardContent>
<Grid container spacing={3}>

View File

@@ -34,11 +34,11 @@ export default function Page(): React.JSX.Element {
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
{t('dashboard.lessonCategories.title')}
{t('title', { ns: 'lesson_category' })}
</Link>
</div>
<div>
<Typography variant="h4">{t('dashboard.lessonCategories.create.title')}</Typography>
<Typography variant="h4">{t('create.title', { ns: 'lesson_category' })}</Typography>
</div>
</Stack>
<LessonCategoryCreateForm />

View File

@@ -0,0 +1,11 @@
# task
## instruction
with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/_helloworld/page.tsx`
with reference to `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_types/edit/[typeId]/page.tsx`
please modify `/home/logic/_wsl_workspace/001_github_ws/lettersoup-online-ws/lettersoup-online/project/002_source/cms/src/app/dashboard/lesson_categories/edit/page.tsx`
please draft a tsx for showing error to user thanks,

View File

@@ -0,0 +1,55 @@
'use client';
import * as React from 'react';
import RouterLink from 'next/link';
import Box from '@mui/material/Box';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/ArrowLeft';
import { useTranslation } from 'react-i18next';
import { paths } from '@/paths';
import { LessonCategoryEditForm } from '@/components/dashboard/lesson_category/lesson-category-edit-form';
// import { LessonCategoryEditForm } from '@/components/dashboard/lesson_category/lesson-category-edit-form';
export default function Page(): React.JSX.Element {
const { t } = useTranslation(['common', 'lesson_category']);
React.useEffect(() => {
console.log('helloworld');
}, []);
return (
<Box
sx={{
maxWidth: 'var(--Content-maxWidth)',
m: 'var(--Content-margin)',
p: 'var(--Content-padding)',
width: 'var(--Content-width)',
}}
>
<Stack spacing={4}>
<Stack spacing={3}>
<div>
<Link
color="text.primary"
component={RouterLink}
href={paths.dashboard.lesson_categories.list}
sx={{ alignItems: 'center', display: 'inline-flex', gap: 1 }}
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
{t('edit.title', { ns: 'lesson_category' })}
</Link>
</div>
<div>
<Typography variant="h4">{t('edit.title', { ns: 'lesson_category' })}</Typography>
</div>
</Stack>
<LessonCategoryEditForm />
</Stack>
</Box>
);
}

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type';
import { LessonCategory } from '@/components/dashboard/lesson_category/types';
// import type { LessonCategory } from '@/components/dashboard/lesson_category/lesson-categories-table';
// import type { LessonCategory } from '@/components/dashboard/lesson_category/interfaces';
@@ -14,6 +14,7 @@ export const lessonCategoriesSampleData = [
quota: 50,
status: 'active',
createdAt: dayjs().subtract(1, 'hour').toDate(),
collectionId: '0000000001',
cat_name: '',
pos: 99,
visible: 'visible',
@@ -30,6 +31,7 @@ export const lessonCategoriesSampleData = [
quota: 100,
status: 'active',
createdAt: dayjs().subtract(3, 'hour').toDate(),
collectionId: '0000000001',
cat_name: '',
pos: 99,
visible: 'visible',
@@ -46,6 +48,7 @@ export const lessonCategoriesSampleData = [
quota: 10,
status: 'blocked',
createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
collectionId: '0000000001',
cat_name: '',
pos: 99,
visible: 'visible',
@@ -62,6 +65,7 @@ export const lessonCategoriesSampleData = [
quota: 0,
status: 'pending',
createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(),
collectionId: '0000000001',
cat_name: '',
pos: 99,
visible: 'visible',
@@ -78,6 +82,7 @@ export const lessonCategoriesSampleData = [
quota: 50,
status: 'active',
createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
collectionId: '0000000001',
cat_name: '',
pos: 99,
visible: 'visible',

View File

@@ -25,7 +25,7 @@ import type { Filters } from '@/components/dashboard/lesson_category/lesson-cate
import { LessonCategoriesPagination } from '@/components/dashboard/lesson_category/lesson-categories-pagination';
import { LessonCategoriesSelectionProvider } from '@/components/dashboard/lesson_category/lesson-categories-selection-context';
import { LessonCategoriesTable } from '@/components/dashboard/lesson_category/lesson-categories-table';
import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
import type { LessonCategory } from '@/components/dashboard/lesson_category/types';
import FormLoading from '@/components/loading';
// import { lessonCategoriesSampleData } from './lesson-categories-sample-data';
@@ -44,7 +44,7 @@ interface PageProps {
}
export default function Page({ searchParams }: PageProps): React.JSX.Element {
const { t } = useTranslation();
const { t } = useTranslation(['common']);
const { email, phone, sortDir, spStatus } = searchParams;
const router = useRouter();
@@ -96,9 +96,9 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
if (showError)
return (
<ErrorDisplay
message={t('"Unable to process request"')}
message={t('error.unable-to-process-request', { ns: 'common' })}
code="500"
details={t('Detailed error information...')}
details={t('error.detailed-error-information', { ns: 'common' })}
/>
);

View File

@@ -36,6 +36,7 @@ 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 { 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';
@@ -43,14 +44,15 @@ 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 FormLoading from '@/components/loading';
export default function Page(): React.JSX.Element {
const { t } = useTranslation();
const { t } = useTranslation(['common']);
const router = useRouter();
//
const { type_id: typeId } = useParams<{ type_id: string }>();
//
const [showLoading, setIsLoading] = React.useState<boolean>(true);
const [showLoading, setShowLoading] = React.useState<boolean>(true);
const [showError, setShowError] = React.useState<boolean>(false);
//
const [showLessonType, setShowLessonType] = React.useState<LessonType>(LessonTypeDefaultValue);
@@ -63,18 +65,31 @@ export default function Page(): React.JSX.Element {
if (typeId) {
pb.collection('LessonsTypes')
.getOne(typeId)
.then((lessonType: RecordModel) => {
setShowLessonType({ ...defaultLessonType, ...lessonType });
setIsLoading(false);
.then((model: RecordModel) => {
setShowLessonType({ ...defaultLessonType, ...model });
})
.catch((err) => {
logger.error(err);
toast(t('dashboard.lessonTypes.list.error'));
setShowError(true);
})
.finally(() => {
setShowLoading(false);
});
}
}, [typeId]);
if (showLoading) return <div>{t('common.loading')}</div>;
if (showLoading) return <FormLoading />;
if (showError)
return (
<ErrorDisplay
message={t('error.unable-to-process-request', { ns: 'common' })}
code="500"
details={t('error.detailed-error-information', { ns: 'common' })}
/>
);
return (
<Box
@@ -96,13 +111,13 @@ export default function Page(): React.JSX.Element {
variant="subtitle2"
>
<ArrowLeftIcon fontSize="var(--icon-fontSize-md)" />
{t('Lesson Types')}
{t('list.title', { ns: 'lesson_type' })}
</Link>
</div>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}>
<Avatar src="/assets/avatar-1.png" sx={{ '--Avatar-size': '64px' }}>
MV
empty
</Avatar>
<div>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>
@@ -145,7 +160,7 @@ export default function Page(): React.JSX.Element {
<UserIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Basic details"
title={t('basic-details', { ns: 'lesson_type' })}
/>
<PropertyList
divider={<Divider />}
@@ -195,7 +210,7 @@ export default function Page(): React.JSX.Element {
<ShieldWarningIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Security"
title={t('security', { ns: 'lesson_type' })}
/>
<CardContent>
<Stack spacing={1}>
@@ -268,7 +283,7 @@ export default function Page(): React.JSX.Element {
<CreditCardIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Billing details"
title={t('billing-details', { ns: 'lesson_type' })}
/>
<CardContent>
<Card sx={{ borderRadius: 1 }} variant="outlined">
@@ -303,7 +318,7 @@ export default function Page(): React.JSX.Element {
<HouseIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
}
title="Shipping addresses"
title={t('shipping-addresses', { ns: 'lesson_type' })}
/>
<CardContent>
<Grid container spacing={3}>

View File

@@ -17,6 +17,7 @@ import { paths } from '@/paths';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import ErrorDisplay from '@/components/dashboard/error';
import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';
import { emptyLessonType, safeAssignment } from '@/components/dashboard/lesson_type/interfaces';
import { LessonTypesFilters } from '@/components/dashboard/lesson_type/lesson-types-filters';
@@ -50,6 +51,8 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
//
const [isLoadingAddPage, setIsLoadingAddPage] = React.useState<boolean>(false);
const [showLoading, setShowLoading] = React.useState<boolean>(true);
const [showError, setShowError] = React.useState<boolean>(false);
const [lessonTypesData, setLessonTypesData] = React.useState<LessonType[]>([]);
// const [recordModel, setRecordModel] = React.useState<RecordModel[]>([]);
const sortedLessonTypes = applySort(lessonTypesData, sortDir);
@@ -86,6 +89,11 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
.catch((err) => {
logger.error(err);
toast(t('dashboard.lessonTypes.list.error'));
setShowError(true);
})
.finally(() => {
setShowLoading(false);
});
};
@@ -93,7 +101,12 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
reloadRows();
}, []);
if (lessonTypesData.length < 1) return <FormLoading />;
if (showLoading) return <FormLoading />;
if (showError)
return (
<ErrorDisplay message={t('unable-to-process-request')} code="500" details={t('detailed-error-information')} />
);
return (
<Box

View File

@@ -1,6 +1,6 @@
import { dayjs } from '@/lib/dayjs';
import type { CreateForm, LessonCategory } from './type';
import type { CreateForm, LessonCategory } from './types';
export const defaultLessonCategory: LessonCategory = {
isEmpty: false,
@@ -14,6 +14,7 @@ export const defaultLessonCategory: LessonCategory = {
description: 'default-description',
remarks: 'default-remarks',
//
collectionId: '0000000000',
createdAt: dayjs('2099-01-01').toDate(),
//
name: '',

View File

@@ -5,7 +5,7 @@ import * as React from 'react';
import { useSelection } from '@/hooks/use-selection';
import type { Selection } from '@/hooks/use-selection';
import type { LessonCategory } from './type';
import type { LessonCategory } from './types';
// import type { LessonCategory } from './lesson-categories-table';
// import type { LessonCategory } from '@/components/dashboard/lesson_category/interfaces';

View File

@@ -28,7 +28,7 @@ import type { ColumnDef } from '@/components/core/data-table';
import ConfirmDeleteModal from './confirm-delete-modal';
// import type { LessonCategory } from './interfaces.1ts';
import { useLessonCategoriesSelection } from './lesson-categories-selection-context';
import { LessonCategory } from './type';
import { LessonCategory } from './types';
function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonCategory>[] {
return [
@@ -43,7 +43,10 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonC
variant="subtitle2"
>
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
<Avatar src={row.avatar} variant="rounded">
<Avatar
src={`http://127.0.0.1:8090/api/files/${row.collectionId}/${row.id}/${row.cat_image}`}
variant="rounded"
>
<ImagesIcon size={32} />
</Avatar>{' '}
<div>
@@ -72,9 +75,6 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonC
name: 'word-count',
width: '100px',
},
{ field: 'phone', name: 'Phone number', width: '100px' },
{
formatter: (row): React.JSX.Element => {
// eslint-disable-next-line react-hooks/rules-of-hooks

View File

@@ -3,8 +3,10 @@
import * as React from 'react';
import RouterLink from 'next/link';
import { useRouter } from 'next/navigation';
import { COL_LESSON_CATEGORIES, NS_LESSON_CATEGORY } from '@/constants';
import { zodResolver } from '@hookform/resolvers/zod';
import { Avatar } from '@mui/material';
import { LoadingButton } from '@mui/lab';
import { Avatar, Divider, MenuItem } from '@mui/material';
// import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
@@ -12,7 +14,6 @@ import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
import Checkbox from '@mui/material/Checkbox';
import Divider from '@mui/material/Divider';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText';
@@ -25,11 +26,13 @@ import Grid from '@mui/material/Unstable_Grid2';
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
// import axios from 'axios';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z as zod } from 'zod';
import { paths } from '@/paths';
import { logger } from '@/lib/default-logger';
import { Option } from '@/components/core/option';
import { TextEditor } from '@/components/core/text-editor/text-editor';
import { toast } from '@/components/core/toaster';
import { fileToBase64 } from './file-to-base64';
@@ -71,6 +74,11 @@ const defaultValues = {
export function LessonCategoryCreateForm(): React.JSX.Element {
const router = useRouter();
const { t } = useTranslation(['common', 'lesson_category']);
const NS_DEFAULT = { ns: 'lesson_category' };
const [isCreating, setIsCreating] = React.useState<boolean>(false);
const {
control,
@@ -81,7 +89,8 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
const onSubmit = React.useCallback(
async (_: Values): Promise<void> => {
async (values: Values): Promise<void> => {
setIsCreating(true);
try {
// Make API request
toast.success('Customer updated');
@@ -115,19 +124,20 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
<CardContent>
<Stack divider={<Divider />} spacing={4}>
<Stack spacing={3}>
<Typography variant="h6">Account information</Typography>
<Typography variant="h6">{t('create.typeInformation', NS_DEFAULT)}</Typography>
<Grid container spacing={3}>
<Grid xs={12}>
<Stack direction="row" spacing={3} sx={{ alignItems: 'center' }}>
<Box
sx={{
border: '1px dashed var(--mui-palette-divider)',
borderRadius: '50%',
borderRadius: '5%',
display: 'inline-flex',
p: '4px',
}}
>
<Avatar
variant="rounded"
src={avatar}
sx={{
'--Avatar-size': '100px',
@@ -143,8 +153,8 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
</Avatar>
</Box>
<Stack spacing={1} sx={{ alignItems: 'flex-start' }}>
<Typography variant="subtitle1">Avatar</Typography>
<Typography variant="caption">Min 400x400px, PNG or JPEG</Typography>
<Typography variant="subtitle1">{t('create.avatar', NS_DEFAULT)}</Typography>
<Typography variant="caption">{t('create.avatarRequirements', NS_DEFAULT)}</Typography>
<Button
color="secondary"
onClick={() => {
@@ -152,7 +162,7 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
}}
variant="outlined"
>
Select
{t('create.avatar_select', NS_DEFAULT)}
</Button>
<input hidden onChange={handleAvatarChange} ref={avatarInputRef} type="file" />
</Stack>
@@ -164,7 +174,7 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
name="name"
render={({ field }) => (
<FormControl error={Boolean(errors.name)} fullWidth>
<InputLabel required>Name</InputLabel>
<InputLabel required>{t('create.name', NS_DEFAULT)}</InputLabel>
<OutlinedInput {...field} />
{errors.name ? <FormHelperText>{errors.name.message}</FormHelperText> : null}
</FormControl>
@@ -213,25 +223,21 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
</Grid>
</Stack>
<Stack spacing={3}>
<Typography variant="h6">Billing information</Typography>
<Typography variant="h6">{t('create.detail-information', NS_DEFAULT)}</Typography>
<Grid container spacing={3}>
<Grid md={6} xs={12}>
<Controller
control={control}
name="billingAddress.country"
render={({ field }) => (
<FormControl error={Boolean(errors.billingAddress?.country)} fullWidth>
<InputLabel required>Country</InputLabel>
<Select {...field}>
<Option value="">Choose a country</Option>
<Option value="us">United States</Option>
<Option value="de">Germany</Option>
<Option value="es">Spain</Option>
</Select>
{errors.billingAddress?.country ? (
<FormHelperText>{errors.billingAddress?.country?.message}</FormHelperText>
) : null}
</FormControl>
<Box>
<Typography variant="subtitle1" color="text-secondary">
{t('create.description', NS_DEFAULT)}
</Typography>
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
<TextEditor content="" placeholder="Write something" />
</Box>
</Box>
)}
/>
</Grid>
@@ -240,134 +246,14 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
control={control}
name="billingAddress.state"
render={({ field }) => (
<FormControl error={Boolean(errors.billingAddress?.state)} fullWidth>
<InputLabel required>State</InputLabel>
<OutlinedInput {...field} />
{errors.billingAddress?.state ? (
<FormHelperText>{errors.billingAddress?.state?.message}</FormHelperText>
) : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="billingAddress.city"
render={({ field }) => (
<FormControl error={Boolean(errors.billingAddress?.city)} fullWidth>
<InputLabel required>City</InputLabel>
<OutlinedInput {...field} />
{errors.billingAddress?.city ? (
<FormHelperText>{errors.billingAddress?.city?.message}</FormHelperText>
) : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="billingAddress.zipCode"
render={({ field }) => (
<FormControl error={Boolean(errors.billingAddress?.zipCode)} fullWidth>
<InputLabel required>Zip code</InputLabel>
<OutlinedInput {...field} />
{errors.billingAddress?.zipCode ? (
<FormHelperText>{errors.billingAddress?.zipCode?.message}</FormHelperText>
) : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="billingAddress.line1"
render={({ field }) => (
<FormControl error={Boolean(errors.billingAddress?.line1)} fullWidth>
<InputLabel required>Address</InputLabel>
<OutlinedInput {...field} />
{errors.billingAddress?.line1 ? (
<FormHelperText>{errors.billingAddress?.line1?.message}</FormHelperText>
) : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="taxId"
render={({ field }) => (
<FormControl error={Boolean(errors.taxId)} fullWidth>
<InputLabel>Tax ID</InputLabel>
<OutlinedInput {...field} placeholder="e.g EU372054390" />
{errors.taxId ? <FormHelperText>{errors.taxId.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Grid>
</Grid>
</Stack>
<Stack spacing={3}>
<Typography variant="h6">Shipping information</Typography>
<FormControlLabel control={<Checkbox defaultChecked />} label="Same as billing address" />
</Stack>
<Stack spacing={3}>
<Typography variant="h6">Additional information</Typography>
<Grid container spacing={3}>
<Grid md={6} xs={12}>
<Controller
control={control}
name="timezone"
render={({ field }) => (
<FormControl error={Boolean(errors.timezone)} fullWidth>
<InputLabel required>Timezone</InputLabel>
<Select {...field}>
<Option value="">Select a timezone</Option>
<Option value="new_york">US - New York</Option>
<Option value="california">US - California</Option>
<Option value="london">UK - London</Option>
</Select>
{errors.timezone ? <FormHelperText>{errors.timezone.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="language"
render={({ field }) => (
<FormControl error={Boolean(errors.language)} fullWidth>
<InputLabel required>Language</InputLabel>
<Select {...field}>
<Option value="">Select a language</Option>
<Option value="en">English</Option>
<Option value="es">Spanish</Option>
<Option value="de">German</Option>
</Select>
{errors.language ? <FormHelperText>{errors.language.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="currency"
render={({ field }) => (
<FormControl error={Boolean(errors.currency)} fullWidth>
<InputLabel>Currency</InputLabel>
<Select {...field}>
<Option value="">Select a currency</Option>
<Option value="USD">USD</Option>
<Option value="EUR">EUR</Option>
<Option value="RON">RON</Option>
</Select>
{errors.currency ? <FormHelperText>{errors.currency.message}</FormHelperText> : null}
</FormControl>
<Box>
<Typography variant="subtitle1" color="text.secondary">
{t('create.remarks', NS_DEFAULT)}
</Typography>
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
<TextEditor content="" placeholder="Write something" />
</Box>
</Box>
)}
/>
</Grid>
@@ -377,11 +263,12 @@ export function LessonCategoryCreateForm(): React.JSX.Element {
</CardContent>
<CardActions sx={{ justifyContent: 'flex-end' }}>
<Button color="secondary" component={RouterLink} href={paths.dashboard.lesson_categories.list}>
Cancel
</Button>
<Button type="submit" variant="contained">
Create customer
{t('create.cancelButton', NS_DEFAULT)}
</Button>
<LoadingButton disabled={isCreating} loading={isCreating} type="submit" variant="contained">
{t('create.createButton', NS_DEFAULT)}
</LoadingButton>
</CardActions>
</Card>
</form>

View File

@@ -0,0 +1,334 @@
'use client';
import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import { COL_LESSON_CATEGORIES, NS_LESSON_CATEGORY } 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 Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
// import Checkbox from '@mui/material/Checkbox';
import FormControl from '@mui/material/FormControl';
// import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText';
import InputLabel from '@mui/material/InputLabel';
import OutlinedInput from '@mui/material/OutlinedInput';
import Select from '@mui/material/Select';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
import 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 { logger } from '@/lib/default-logger';
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 { fileToBase64 } from './file-to-base64';
import { LessonCategory, EditFormProps } from './types';
const schema = zod.object({
cat_name: zod.string().min(1, 'Name is required').max(255),
type: zod.string().min(1, 'Name is required').max(255),
pos: zod.number().min(1, 'Phone is required').max(15),
visible: zod.string().max(255),
description: zod.string().optional(),
remarks: zod.string().optional(),
});
type Values = zod.infer<typeof schema>;
const defaultValues = {
cat_name: '',
type: '',
pos: 1,
visible: 'visible',
} satisfies Values;
export function LessonCategoryEditForm(): React.JSX.Element {
const router = useRouter();
const { t } = useTranslation(['common', 'lesson_category']);
const NS_DEFAULT = { ns: 'lesson_category' };
const { cat_id: catId } = useParams<{ cat_id: string }>();
//
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
const [showLoading, setShowLoading] = React.useState<boolean>(false);
const [showError, setShowError] = React.useState<boolean>(false);
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
watch,
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
const onSubmit = React.useCallback(async (values: Values): Promise<void> => {
// setIsUpdating(true);
// const tempUpdate: LessonCategoryEditFormProps = {
// name: values.cat_name,
// type: values.type,
// pos: values.pos,
// visible: values.visible ? 'visible' : 'hidden',
// };
// pb.collection(COL_LESSON_CATEGORIES)
// .update(catId, tempUpdate)
// .then((res) => {
// logger.debug(res);
// toast.success(t('update.success', NS_DEFAULT));
// setIsUpdating(false);
// router.push(paths.dashboard.lesson_categories.list);
// })
// .catch((err) => {
// logger.error(err);
// toast.error('Something went wrong!');
// })
// .finally(() => {
// //
// setIsUpdating(false);
// });
}, []);
const avatarInputRef = React.useRef<HTMLInputElement>(null);
// const avatar = watch('avatar');
const handleAvatarChange = React.useCallback(
async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const url = await fileToBase64(file);
// setValue('avatar', url);
}
},
[setValue]
);
const [textDescription, setTextDescription] = React.useState<string>('loading');
const handleLoad = React.useCallback(
(id: string) => {
setShowLoading(true);
pb.collection(COL_LESSON_CATEGORIES)
.getOne(id)
.then((model: RecordModel) => {
const temp: LessonCategory = { ...defaultLessonCategory, ...model };
reset(temp);
setTextDescription(temp.description);
})
.catch((err) => {
logger.error(err);
toast(t('list.error', NS_DEFAULT));
})
.finally(() => {
setShowLoading(false);
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[catId]
);
React.useEffect(() => {
handleLoad(catId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [catId]);
if (showLoading) return <FormLoading />;
if (showError)
return (
<ErrorDisplay
message={t('error.unable-to-process-request', NS_DEFAULT)}
code="500"
details={t('error.detailed-error-information', NS_DEFAULT)}
/>
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Card>
<CardContent>
<Stack divider={<Divider />} spacing={4}>
<Stack spacing={3}>
<Typography variant="h6">{t('edit.basic-info', NS_DEFAULT)}</Typography>
<Grid container spacing={3}>
<Grid xs={12}>
<Stack direction="row" spacing={3} sx={{ alignItems: 'center' }}>
<Box
sx={{
border: '1px dashed var(--mui-palette-divider)',
borderRadius: '5%',
display: 'inline-flex',
p: '4px',
}}
>
{/*
<Avatar
src={avatar}
sx={{
'--Avatar-size': '100px',
'--Icon-fontSize': 'var(--icon-fontSize-lg)',
alignItems: 'center',
bgcolor: 'var(--mui-palette-background-level1)',
color: 'var(--mui-palette-text-primary)',
display: 'flex',
justifyContent: 'center',
}}
>
// TODO: resume me
<CameraIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
*/}
</Box>
<Stack spacing={1} sx={{ alignItems: 'flex-start' }}>
<Typography variant="subtitle1">{t('edit.avatar', NS_DEFAULT)}</Typography>
<Typography variant="caption">{t('edit.avatarRequirements', NS_DEFAULT)}</Typography>
<Button
color="secondary"
onClick={() => {
avatarInputRef.current?.click();
}}
variant="outlined"
>
{t('edit.select', NS_DEFAULT)}
</Button>
<input hidden onChange={handleAvatarChange} ref={avatarInputRef} type="file" />
</Stack>
</Stack>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="cat_name"
render={({ field }) => (
<FormControl error={Boolean(errors.cat_name)} fullWidth>
<InputLabel required>{t('edit.name', NS_DEFAULT)}</InputLabel>
<OutlinedInput {...field} />
{errors.cat_name ? <FormHelperText>{errors.cat_name.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="pos"
render={({ field }) => (
<FormControl error={Boolean(errors.pos)} fullWidth>
<InputLabel required>{t('edit.position', NS_DEFAULT)}</InputLabel>
<OutlinedInput
{...field}
onChange={(e) => {
field.onChange(parseInt(e.target.value));
}}
type="number"
/>
{errors.pos ? <FormHelperText>{errors.pos.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="visible"
render={({ field }) => (
<FormControl error={Boolean(errors.visible)} fullWidth>
<InputLabel>{t('edit.visible', NS_DEFAULT)}</InputLabel>
<Select {...field}>
<MenuItem value="visible">{t('edit.visible', NS_DEFAULT)}</MenuItem>
<MenuItem value="hidden">{t('edit.hidden', NS_DEFAULT)}</MenuItem>
</Select>
{errors.visible ? <FormHelperText>{errors.visible.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Grid>
</Grid>
</Stack>
<Stack spacing={3}>
<Typography variant="h6">{t('create.detail-information', NS_DEFAULT)}</Typography>
<Grid container spacing={3}>
<Grid md={6} xs={12}>
<Controller
control={control}
name="description"
render={({ field }) => {
console.log({ field });
return (
<Box>
<Typography variant="subtitle1" color="text-secondary">
{t('create.description', NS_DEFAULT)}
</Typography>
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
{JSON.stringify({ field })}
<TextEditor
{...field}
content={textDescription}
onUpdate={({ editor }) => {
console.log(editor.getHTML());
field.onChange({ target: { value: editor.getHTML() } });
}}
placeholder={t('edit.write-something', NS_DEFAULT)}
/>
</Box>
</Box>
);
}}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
control={control}
name="billingAddress.state"
render={({ field }) => (
<Box>
<Typography variant="subtitle1" color="text.secondary">
{t('create.remarks', NS_DEFAULT)}
</Typography>
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
<TextEditor content="" placeholder="Write something" />
</Box>
</Box>
)}
/>
</Grid>
</Grid>
</Stack>
</Stack>
</CardContent>
<CardActions sx={{ justifyContent: 'flex-end' }}>
<Button color="secondary" component={RouterLink} href={paths.dashboard.lesson_types.list}>
{t('edit.cancelButton', NS_DEFAULT)}
</Button>
<LoadingButton disabled={isUpdating} loading={isUpdating} type="submit" variant="contained">
{t('edit.updateButton', NS_DEFAULT)}
</LoadingButton>
</CardActions>
</Card>
</form>
);
}

View File

@@ -0,0 +1,352 @@
'use client';
import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import { COL_LESSON_CATEGORIES, NS_LESSON_CATEGORY } 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 Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
// import Checkbox from '@mui/material/Checkbox';
import FormControl from '@mui/material/FormControl';
// import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText';
import InputLabel from '@mui/material/InputLabel';
import OutlinedInput from '@mui/material/OutlinedInput';
import Select from '@mui/material/Select';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Grid from '@mui/material/Unstable_Grid2';
import { Camera as CameraIcon } from '@phosphor-icons/react/dist/ssr/Camera';
import 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 { 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 { fileToBase64 } from './file-to-base64';
import { EditFormProps, LessonCategory } from './types';
// TODO: review this
const schema = zod.object({
cat_name: zod.string().min(1, 'name-is-required').max(255),
//
pos: zod.number().min(1, 'Phone is required').max(99),
visible: zod.string().max(255),
//
description: zod.string().optional(),
remarks: zod.string().optional(),
});
type Values = zod.infer<typeof schema>;
const defaultValues = {
cat_name: '',
// cat_image: undefined,
pos: 0,
visible: 'hidden',
// lesson_id: 'default-lesson-id',
description: 'default-description',
remarks: 'default-remarks',
//
} satisfies Values;
export function LessonCategoryEditForm(): React.JSX.Element {
const router = useRouter();
const { t } = useTranslation(['common', 'lesson_category']);
const NS_DEFAULT = { ns: 'lesson_category' };
const { cat_id: catId } = useParams<{ cat_id: string }>();
//
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
const [showLoading, setShowLoading] = React.useState<boolean>(false);
const [showError, setShowError] = React.useState<boolean>(false);
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
watch,
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
const onSubmit = React.useCallback(async (values: Values): Promise<void> => {
setIsUpdating(true);
const tempUpdate: EditFormProps = {
cat_name: values.cat_name,
pos: values.pos,
visible: values.visible,
description: values.description,
remarks: values.remarks,
};
pb.collection(COL_LESSON_CATEGORIES)
.update(catId, tempUpdate)
.then((res) => {
logger.debug(res);
toast.success(t('update.success', NS_DEFAULT));
router.push(paths.dashboard.lesson_categories.list);
})
.catch((err) => {
logger.error(err);
toast.error('Something went wrong!');
})
.finally(() => {
//
setIsUpdating(false);
});
}, []);
const avatarInputRef = React.useRef<HTMLInputElement>(null);
// const avatar = watch('avatar');
const handleAvatarChange = React.useCallback(
async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const url = await fileToBase64(file);
// setValue('avatar', url);
}
},
[setValue]
);
const [textDescription, setTextDescription] = React.useState<string>('loading');
const [textRemarks, setTextRemarks] = React.useState<string>('loading');
const handleLoad = React.useCallback(
(id: string) => {
setShowLoading(true);
pb.collection(COL_LESSON_CATEGORIES)
.getOne(id)
.then((model: RecordModel) => {
const temp: LessonCategory = { ...defaultLessonCategory, ...model };
reset(temp);
setTextDescription(temp.description);
setTextRemarks(temp.remarks);
})
.catch((err) => {
logger.error(err);
toast(t('list.error', NS_DEFAULT));
})
.finally(() => {
setShowLoading(false);
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[catId]
);
React.useEffect(() => {
handleLoad(catId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [catId]);
if (showLoading) return <FormLoading />;
if (showError)
return (
<ErrorDisplay
message={t('error.unable-to-process-request', NS_DEFAULT)}
code="500"
details={t('error.detailed-error-information', NS_DEFAULT)}
/>
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Card>
<CardContent>
<Stack divider={<Divider />} spacing={4}>
<Stack spacing={3}>
<Typography variant="h6">{t('edit.basic-info', NS_DEFAULT)}</Typography>
<Grid container spacing={3}>
<Grid xs={12}>
<Stack direction="row" spacing={3} sx={{ alignItems: 'center' }}>
<Box
sx={{
border: '1px dashed var(--mui-palette-divider)',
borderRadius: '5%',
display: 'inline-flex',
p: '4px',
}}
>
{/*
<Avatar
src={avatar}
sx={{
'--Avatar-size': '100px',
'--Icon-fontSize': 'var(--icon-fontSize-lg)',
alignItems: 'center',
bgcolor: 'var(--mui-palette-background-level1)',
color: 'var(--mui-palette-text-primary)',
display: 'flex',
justifyContent: 'center',
}}
>
// TODO: resume me
<CameraIcon fontSize="var(--Icon-fontSize)" />
</Avatar>
*/}
</Box>
<Stack spacing={1} sx={{ alignItems: 'flex-start' }}>
<Typography variant="subtitle1">{t('edit.avatar', NS_DEFAULT)}</Typography>
<Typography variant="caption">{t('edit.avatarRequirements', NS_DEFAULT)}</Typography>
<Button
color="secondary"
onClick={() => {
avatarInputRef.current?.click();
}}
variant="outlined"
>
{t('edit.select', NS_DEFAULT)}
</Button>
<input hidden onChange={handleAvatarChange} ref={avatarInputRef} type="file" />
</Stack>
</Stack>
</Grid>
<Grid md={6} xs={12}>
<Controller
disabled={isUpdating}
control={control}
name="cat_name"
render={({ field }) => (
<FormControl disabled={isUpdating} error={Boolean(errors.cat_name)} fullWidth>
<InputLabel required>{t('edit.name', NS_DEFAULT)}</InputLabel>
<OutlinedInput {...field} />
{errors.cat_name ? <FormHelperText>{errors.cat_name.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
disabled={isUpdating}
control={control}
name="pos"
render={({ field }) => (
<FormControl error={Boolean(errors.pos)} fullWidth>
<InputLabel required>{t('edit.position', NS_DEFAULT)}</InputLabel>
<OutlinedInput
{...field}
onChange={(e) => {
field.onChange(parseInt(e.target.value));
}}
type="number"
/>
{errors.pos ? <FormHelperText>{errors.pos.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
disabled={isUpdating}
control={control}
name="visible"
render={({ field }) => (
<FormControl error={Boolean(errors.visible)} fullWidth>
<InputLabel>{t('edit.visible', NS_DEFAULT)}</InputLabel>
<Select {...field}>
<MenuItem value="visible">{t('edit.visible', NS_DEFAULT)}</MenuItem>
<MenuItem value="hidden">{t('edit.hidden', NS_DEFAULT)}</MenuItem>
</Select>
{errors.visible ? <FormHelperText>{errors.visible.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Grid>
</Grid>
</Stack>
<Stack spacing={3}>
<Typography variant="h6">{t('create.detail-information', NS_DEFAULT)}</Typography>
<Grid container spacing={3}>
<Grid md={6} xs={12}>
<Controller
disabled={isUpdating}
control={control}
name="description"
render={({ field }) => {
return (
<Box>
<Typography variant="subtitle1" color="text-secondary">
{t('create.description', NS_DEFAULT)}
</Typography>
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
<TextEditor
{...field}
content={textDescription}
onUpdate={({ editor }) => {
field.onChange({ target: { value: editor.getHTML() } });
}}
placeholder={t('edit.write-something', NS_DEFAULT)}
/>
</Box>
</Box>
);
}}
/>
</Grid>
<Grid md={6} xs={12}>
<Controller
disabled={isUpdating}
control={control}
name="remarks"
render={({ field }) => (
<Box>
<Typography variant="subtitle1" color="text.secondary">
{t('create.remarks', NS_DEFAULT)}
</Typography>
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '400px' } }}>
<TextEditor
content={textRemarks}
onUpdate={({ editor }) => {
field.onChange({ target: { value: editor.getText() } });
}}
hideToolbar={true}
placeholder={t('edit.write-something', NS_DEFAULT)}
/>
</Box>
</Box>
)}
/>
</Grid>
</Grid>
</Stack>
</Stack>
</CardContent>
<CardActions sx={{ justifyContent: 'flex-end' }}>
<Button color="secondary" component={RouterLink} href={paths.dashboard.lesson_types.list}>
{t('edit.cancelButton', NS_DEFAULT)}
</Button>
<LoadingButton disabled={isUpdating} loading={isUpdating} type="submit" variant="contained">
{t('edit.updateButton', NS_DEFAULT)}
</LoadingButton>
</CardActions>
</Card>
</form>
);
}

View File

@@ -10,6 +10,7 @@ export interface LessonCategory {
lesson_id: string;
description: string;
remarks: string;
collectionId: string;
//
name: string;
avatar: string;
@@ -26,3 +27,11 @@ export interface CreateForm {
pos: number;
visible: string;
}
export interface EditFormProps {
cat_name: string;
pos: number;
visible: string;
description?: string;
remarks?: string;
}

View File

@@ -60,6 +60,7 @@ const defaultValues = {
export function LessonTypeCreateForm(): React.JSX.Element {
const router = useRouter();
const { t } = useTranslation();
const [isCreating, setIsCreating] = React.useState<boolean>(false);
const {
@@ -79,7 +80,6 @@ export function LessonTypeCreateForm(): React.JSX.Element {
pb.collection(COL_LESSON_TYPES)
.create(data)
.then((res) => {
console.log(res);
logger.debug(res);
toast.success(t('dashboard.lessonTypes.update.success'));
setIsCreating(false);
@@ -90,23 +90,6 @@ export function LessonTypeCreateForm(): React.JSX.Element {
toast.error('Something went wrong!');
setIsCreating(false);
});
// const tempCreate: LessonTypeCreateForm = LessonTypeCreateFormDefault;
// tempCreate.name = values.name;
// tempCreate.type = values.type;
// tempCreate.pos = 1;
// tempCreate.visible = 'visible';
// createLessonType(tempCreate)
// .then((res) => {
// router.push(paths.dashboard.lesson_types.list);
// toast.success(t('dashboard.lessonTypes.create.success'));
// })
// .catch((err) => {
// logger.error(err);
// toast.error(t('dashboard.lessonTypes.create.error'));
// setIsCreating(false);
// });
},
[router]
);

View File

@@ -6,7 +6,7 @@ import { useParams, useRouter } from 'next/navigation';
import { COL_LESSON_TYPES } from '@/constants';
import { zodResolver } from '@hookform/resolvers/zod';
import { LoadingButton } from '@mui/lab';
import { MenuItem } from '@mui/material';
import { Avatar, MenuItem } from '@mui/material';
// import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
@@ -35,6 +35,7 @@ import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
// import { Option } from '@/components/core/option';
import { toast } from '@/components/core/toaster';
import FormLoading from '@/components/loading';
// import { getLessonTypeById, updateLessonType } from './http-actions';
// TODO: this may be wrong
@@ -74,8 +75,9 @@ export function LessonTypeEditForm(): React.JSX.Element {
const router = useRouter();
const { t } = useTranslation();
const { typeId } = useParams<{ typeId: string }>();
//
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [showLoading, setShowLoading] = React.useState<boolean>(false);
const {
control,
@@ -83,7 +85,7 @@ export function LessonTypeEditForm(): React.JSX.Element {
formState: { errors },
setValue,
reset,
// watch,
watch,
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
const onSubmit = React.useCallback(async (values: Values): Promise<void> => {
@@ -129,10 +131,10 @@ export function LessonTypeEditForm(): React.JSX.Element {
(id: string) => {
pb.collection(COL_LESSON_TYPES)
.getOne(id)
.then((lessonType: RecordModel) => {
reset({ ...defaultLessonType, ...lessonType });
.then((model: RecordModel) => {
reset({ ...defaultLessonType, ...model });
setIsLoading(false);
setShowLoading(false);
})
.catch((err) => {
logger.error(err);
@@ -148,7 +150,7 @@ export function LessonTypeEditForm(): React.JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [typeId]);
if (isLoading) return <>loading</>;
if (showLoading) return <FormLoading />;
return (
<form onSubmit={handleSubmit(onSubmit)}>

View File

@@ -2,5 +2,6 @@ const COL_LESSON_TYPES = 'LessonsTypes';
const COL_LESSON_CATEGORIES = 'LessonsCategories';
const NO_VALUE = 'NO_VALUE';
const NO_NUM = -Infinity;
const NS_LESSON_CATEGORY = 'lesson_category';
export { COL_LESSON_TYPES, NO_VALUE, NO_NUM, COL_LESSON_CATEGORIES };
export { COL_LESSON_TYPES, NO_VALUE, NO_NUM, COL_LESSON_CATEGORIES, NS_LESSON_CATEGORY };

View File

@@ -7,7 +7,6 @@ import { initReactI18next } from 'react-i18next';
import { logger } from '@/lib/default-logger';
//
// .
// .use(detector)
// use(detector);