"``refactor student create and edit forms with translation support, update schema validation, and use new database operations for student management
``"
This commit is contained in:
@@ -1,9 +1,17 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
// src/components/dashboard/student/student-create-form.tsx
|
||||||
|
//
|
||||||
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 { useRouter } from 'next/navigation';
|
||||||
|
import { UpdateBillingAddressById } from '@/db/billingAddress/UpdateById';
|
||||||
|
import { createStudent } from '@/db/Students/Create';
|
||||||
|
import { getStudentById } from '@/db/Students/GetById';
|
||||||
|
import { UpdateStudentById } from '@/db/Students/UpdateById';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
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 Button from '@mui/material/Button';
|
||||||
@@ -16,41 +24,38 @@ import FormControl from '@mui/material/FormControl';
|
|||||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
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 MenuItem from '@mui/material/MenuItem';
|
||||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||||
import Select from '@mui/material/Select';
|
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 { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
|
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 isDevelopment from '@/lib/check-is-development';
|
||||||
import { logger } from '@/lib/default-logger';
|
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 { Option } from '@/components/core/option';
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
import { createCustomer } from '@/db/Customers/Create';
|
import FormLoading from '@/components/loading';
|
||||||
import isDevelopment from '@/lib/check-is-development';
|
|
||||||
|
|
||||||
function fileToBase64(file: Blob): Promise<string> {
|
// import ErrorDisplay from '../../error';
|
||||||
return new Promise((resolve, reject) => {
|
import ErrorDisplay from '../error';
|
||||||
const reader = new FileReader();
|
import { CreateFormProps, Student } from './type.d';
|
||||||
reader.readAsDataURL(file);
|
|
||||||
reader.onload = () => {
|
|
||||||
resolve(reader.result as string);
|
|
||||||
};
|
|
||||||
reader.onerror = () => {
|
|
||||||
reject(new Error('Error converting file to base64'));
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// TODO: review schema
|
||||||
const schema = zod.object({
|
const schema = zod.object({
|
||||||
avatar: zod.string().optional(),
|
|
||||||
name: zod.string().min(1, 'Name is required').max(255),
|
name: zod.string().min(1, 'Name is required').max(255),
|
||||||
email: zod.string().email('Must be a valid email').min(1, 'Email is required').max(255),
|
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),
|
phone: zod.string().min(1, 'Phone is required').max(25),
|
||||||
company: zod.string().max(255),
|
company: zod.string().max(255).optional(),
|
||||||
billingAddress: zod.object({
|
billingAddress: zod.object({
|
||||||
country: zod.string().min(1, 'Country is required').max(255),
|
country: zod.string().min(1, 'Country is required').max(255),
|
||||||
state: zod.string().min(1, 'State is required').max(255),
|
state: zod.string().min(1, 'State is required').max(255),
|
||||||
@@ -63,12 +68,12 @@ const schema = zod.object({
|
|||||||
timezone: zod.string().min(1, 'Timezone is required').max(255),
|
timezone: zod.string().min(1, 'Timezone is required').max(255),
|
||||||
language: zod.string().min(1, 'Language is required').max(255),
|
language: zod.string().min(1, 'Language is required').max(255),
|
||||||
currency: zod.string().min(1, 'Currency is required').max(255),
|
currency: zod.string().min(1, 'Currency is required').max(255),
|
||||||
|
avatar: zod.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Values = zod.infer<typeof schema>;
|
type Values = zod.infer<typeof schema>;
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
avatar: '',
|
|
||||||
name: 'new name',
|
name: 'new name',
|
||||||
email: '123@123.com',
|
email: '123@123.com',
|
||||||
phone: '91234567',
|
phone: '91234567',
|
||||||
@@ -85,10 +90,18 @@ const defaultValues = {
|
|||||||
timezone: 'new_york',
|
timezone: 'new_york',
|
||||||
language: 'en',
|
language: 'en',
|
||||||
currency: 'USD',
|
currency: 'USD',
|
||||||
|
avatar: '',
|
||||||
} satisfies Values;
|
} satisfies Values;
|
||||||
|
|
||||||
export function CustomerCreateForm(): React.JSX.Element {
|
export function StudentCreateForm(): React.JSX.Element {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation(['students']);
|
||||||
|
|
||||||
|
//
|
||||||
|
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
|
||||||
|
const [showLoading, setShowLoading] = React.useState<boolean>(false);
|
||||||
|
//
|
||||||
|
const [showError, setShowError] = React.useState({ show: false, detail: '' });
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@@ -100,14 +113,35 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
|
|
||||||
const onSubmit = React.useCallback(
|
const onSubmit = React.useCallback(
|
||||||
async (values: Values): Promise<void> => {
|
async (values: Values): Promise<void> => {
|
||||||
|
// Use standard create method from db/Customers/Create
|
||||||
|
const tempCreate: CreateFormProps = {
|
||||||
|
avatar: values.avatar ? await base64ToFile(values.avatar) : null,
|
||||||
|
//
|
||||||
|
name: values.name,
|
||||||
|
email: values.email,
|
||||||
|
phone: values.phone,
|
||||||
|
company: values.company,
|
||||||
|
timezone: values.timezone,
|
||||||
|
language: values.language,
|
||||||
|
currency: values.currency,
|
||||||
|
taxId: values.taxId,
|
||||||
|
state: 'pending',
|
||||||
|
meta: {},
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use standard create method from db/Customers/Create
|
// if (billingAddressId) {
|
||||||
const record = await createCustomer(values);
|
// await UpdateBillingAddressById(billingAddressId, values.billingAddress);
|
||||||
toast.success('Customer created');
|
// }
|
||||||
router.push(paths.dashboard.students.view(record.id));
|
|
||||||
|
const record = await createStudent(tempCreate);
|
||||||
|
toast.success('Student created');
|
||||||
|
// router.push(paths.dashboard.students.view(record.id));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
toast.error('Failed to create customer');
|
toast.error('Failed to create Student');
|
||||||
|
} finally {
|
||||||
|
setIsUpdating(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[router]
|
[router]
|
||||||
@@ -137,7 +171,7 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
spacing={4}
|
spacing={4}
|
||||||
>
|
>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Typography variant="h6">Account information</Typography>
|
<Typography variant="h6">{t('create.basic-info')}</Typography>
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
spacing={3}
|
spacing={3}
|
||||||
@@ -151,12 +185,13 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
border: '1px dashed var(--mui-palette-divider)',
|
border: '1px dashed var(--mui-palette-divider)',
|
||||||
borderRadius: '50%',
|
borderRadius: '5%',
|
||||||
display: 'inline-flex',
|
display: 'inline-flex',
|
||||||
p: '4px',
|
p: '4px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
variant="rounded"
|
||||||
src={avatar}
|
src={avatar}
|
||||||
sx={{
|
sx={{
|
||||||
'--Avatar-size': '100px',
|
'--Avatar-size': '100px',
|
||||||
@@ -175,8 +210,8 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
spacing={1}
|
spacing={1}
|
||||||
sx={{ alignItems: 'flex-start' }}
|
sx={{ alignItems: 'flex-start' }}
|
||||||
>
|
>
|
||||||
<Typography variant="subtitle1">Avatar</Typography>
|
<Typography variant="subtitle1">{t('create.avatar')}</Typography>
|
||||||
<Typography variant="caption">Min 400x400px, PNG or JPEG</Typography>
|
<Typography variant="caption">{t('create.avatarRequirements')}</Typography>
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -184,7 +219,7 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
}}
|
}}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
>
|
>
|
||||||
Select
|
{t('create.avatar_select')}
|
||||||
</Button>
|
</Button>
|
||||||
<input
|
<input
|
||||||
hidden
|
hidden
|
||||||
@@ -226,7 +261,7 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
error={Boolean(errors.email)}
|
error={Boolean(errors.email)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>Email address</InputLabel>
|
<InputLabel required>{t('create.email-address')}</InputLabel>
|
||||||
<OutlinedInput
|
<OutlinedInput
|
||||||
{...field}
|
{...field}
|
||||||
type="email"
|
type="email"
|
||||||
@@ -248,7 +283,7 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
error={Boolean(errors.phone)}
|
error={Boolean(errors.phone)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>Phone number</InputLabel>
|
<InputLabel required>{t('create.phone-number')}</InputLabel>
|
||||||
<OutlinedInput {...field} />
|
<OutlinedInput {...field} />
|
||||||
{errors.phone ? <FormHelperText>{errors.phone.message}</FormHelperText> : null}
|
{errors.phone ? <FormHelperText>{errors.phone.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -268,7 +303,10 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel>Company</InputLabel>
|
<InputLabel>Company</InputLabel>
|
||||||
<OutlinedInput {...field} />
|
<OutlinedInput
|
||||||
|
{...field}
|
||||||
|
placeholder="no company name"
|
||||||
|
/>
|
||||||
{errors.company ? <FormHelperText>{errors.company.message}</FormHelperText> : null}
|
{errors.company ? <FormHelperText>{errors.company.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
@@ -276,8 +314,9 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
{/* */}
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Typography variant="h6">Billing information</Typography>
|
<Typography variant="h6">{t('create.billing-information')}</Typography>
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
spacing={3}
|
spacing={3}
|
||||||
@@ -296,10 +335,12 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
>
|
>
|
||||||
<InputLabel required>Country</InputLabel>
|
<InputLabel required>Country</InputLabel>
|
||||||
<Select {...field}>
|
<Select {...field}>
|
||||||
<Option value="">Choose a country</Option>
|
<MenuItem value="">Choose a country</MenuItem>
|
||||||
<Option value="us">United States</Option>
|
<MenuItem value="US">United States</MenuItem>
|
||||||
<Option value="de">Germany</Option>
|
<MenuItem value="UK">United Kingdom</MenuItem>
|
||||||
<Option value="es">Spain</Option>
|
<MenuItem value="CA">Canada</MenuItem>
|
||||||
|
<MenuItem value="DE">Germany</MenuItem>
|
||||||
|
<MenuItem value="ES">Spain</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
{errors.billingAddress?.country ? (
|
{errors.billingAddress?.country ? (
|
||||||
<FormHelperText>{errors.billingAddress?.country?.message}</FormHelperText>
|
<FormHelperText>{errors.billingAddress?.country?.message}</FormHelperText>
|
||||||
@@ -362,7 +403,7 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
error={Boolean(errors.billingAddress?.zipCode)}
|
error={Boolean(errors.billingAddress?.zipCode)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>Zip code</InputLabel>
|
<InputLabel required>{t('create.zip-code')}</InputLabel>
|
||||||
<OutlinedInput {...field} />
|
<OutlinedInput {...field} />
|
||||||
{errors.billingAddress?.zipCode ? (
|
{errors.billingAddress?.zipCode ? (
|
||||||
<FormHelperText>{errors.billingAddress?.zipCode?.message}</FormHelperText>
|
<FormHelperText>{errors.billingAddress?.zipCode?.message}</FormHelperText>
|
||||||
@@ -383,7 +424,7 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
error={Boolean(errors.billingAddress?.line1)}
|
error={Boolean(errors.billingAddress?.line1)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>Address</InputLabel>
|
<InputLabel required>{t('create.address-line-1')}</InputLabel>
|
||||||
<OutlinedInput {...field} />
|
<OutlinedInput {...field} />
|
||||||
{errors.billingAddress?.line1 ? (
|
{errors.billingAddress?.line1 ? (
|
||||||
<FormHelperText>{errors.billingAddress?.line1?.message}</FormHelperText>
|
<FormHelperText>{errors.billingAddress?.line1?.message}</FormHelperText>
|
||||||
@@ -424,7 +465,7 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Typography variant="h6">Additional information</Typography>
|
<Typography variant="h6">{t('create.additional-information')}</Typography>
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
spacing={3}
|
spacing={3}
|
||||||
@@ -443,10 +484,14 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
>
|
>
|
||||||
<InputLabel required>Timezone</InputLabel>
|
<InputLabel required>Timezone</InputLabel>
|
||||||
<Select {...field}>
|
<Select {...field}>
|
||||||
<Option value="">Select a timezone</Option>
|
<MenuItem value="">Select a timezone</MenuItem>
|
||||||
<Option value="new_york">US - New York</Option>
|
<MenuItem value="Europe/London">London</MenuItem>
|
||||||
<Option value="california">US - California</Option>
|
<MenuItem value="Asia/Tokyo">Tokyo</MenuItem>
|
||||||
<Option value="london">UK - London</Option>
|
<MenuItem value="America/Boa_Vista">Boa Vista</MenuItem>
|
||||||
|
<MenuItem value="America/Grand_Turk">Grand Turk</MenuItem>
|
||||||
|
<MenuItem value="Asia/Manila">Manila</MenuItem>
|
||||||
|
<MenuItem value="Asia/Urumqi">Urumqi</MenuItem>
|
||||||
|
<MenuItem value="Africa/Tunis">Tunis</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
{errors.timezone ? <FormHelperText>{errors.timezone.message}</FormHelperText> : null}
|
{errors.timezone ? <FormHelperText>{errors.timezone.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -467,10 +512,11 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
>
|
>
|
||||||
<InputLabel required>Language</InputLabel>
|
<InputLabel required>Language</InputLabel>
|
||||||
<Select {...field}>
|
<Select {...field}>
|
||||||
<Option value="">Select a language</Option>
|
<MenuItem value="">Select a language</MenuItem>
|
||||||
<Option value="en">English</Option>
|
<MenuItem value="en">English</MenuItem>
|
||||||
<Option value="es">Spanish</Option>
|
<MenuItem value="es">Spanish</MenuItem>
|
||||||
<Option value="de">German</Option>
|
<MenuItem value="de">German</MenuItem>
|
||||||
|
<MenuItem value="fr">French</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
{errors.language ? <FormHelperText>{errors.language.message}</FormHelperText> : null}
|
{errors.language ? <FormHelperText>{errors.language.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -489,12 +535,12 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
error={Boolean(errors.currency)}
|
error={Boolean(errors.currency)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel>Currency</InputLabel>
|
<InputLabel required>{t('create.currency')}</InputLabel>
|
||||||
<Select {...field}>
|
<Select {...field}>
|
||||||
<Option value="">Select a currency</Option>
|
<MenuItem value="">no currency selected</MenuItem>
|
||||||
<Option value="USD">USD</Option>
|
<MenuItem value="USD">USD</MenuItem>
|
||||||
<Option value="EUR">EUR</Option>
|
<MenuItem value="EUR">EUR</MenuItem>
|
||||||
<Option value="RON">RON</Option>
|
<MenuItem value="GBP">GBP</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
{errors.currency ? <FormHelperText>{errors.currency.message}</FormHelperText> : null}
|
{errors.currency ? <FormHelperText>{errors.currency.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -511,14 +557,17 @@ export function CustomerCreateForm(): React.JSX.Element {
|
|||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
href={paths.dashboard.students.list}
|
href={paths.dashboard.students.list}
|
||||||
>
|
>
|
||||||
Cancel
|
{t('create.cancelButton')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
|
<LoadingButton
|
||||||
|
disabled={isUpdating}
|
||||||
|
loading={isUpdating}
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
>
|
>
|
||||||
Create customer
|
{t('create.updateButton')}
|
||||||
</Button>
|
</LoadingButton>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</Card>
|
</Card>
|
||||||
<Box sx={{ display: isDevelopment ? 'block' : 'none' }}>
|
<Box sx={{ display: isDevelopment ? 'block' : 'none' }}>
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
// src/components/dashboard/student/student-edit-form.tsx
|
||||||
|
//
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import RouterLink from 'next/link';
|
import RouterLink from 'next/link';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
//
|
//
|
||||||
import { COL_CUSTOMERS } from '@/constants';
|
import { COL_CUSTOMERS, COL_USER_METAS } from '@/constants';
|
||||||
|
import { UpdateBillingAddressById } from '@/db/billingAddress/UpdateById';
|
||||||
|
import { getStudentById } from '@/db/Students/GetById';
|
||||||
|
import { UpdateStudentById } from '@/db/Students/UpdateById';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
//
|
//
|
||||||
@@ -87,14 +92,15 @@ const defaultValues = {
|
|||||||
|
|
||||||
export function StudentEditForm(): React.JSX.Element {
|
export function StudentEditForm(): React.JSX.Element {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation(['lp_categories']);
|
const { t } = useTranslation(['students']);
|
||||||
|
|
||||||
const { customerId } = useParams<{ customerId: string }>();
|
const { id: studentId } = useParams<{ id: string }>();
|
||||||
//
|
//
|
||||||
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
|
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
|
||||||
const [showLoading, setShowLoading] = React.useState<boolean>(false);
|
const [showLoading, setShowLoading] = React.useState<boolean>(false);
|
||||||
//
|
//
|
||||||
const [showError, setShowError] = React.useState({ show: false, detail: '' });
|
const [showError, setShowError] = React.useState({ show: false, detail: '' });
|
||||||
|
const [billingAddressId, setBillingAddressId] = React.useState<string | null>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@@ -110,30 +116,38 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
setIsUpdating(true);
|
setIsUpdating(true);
|
||||||
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
|
avatar: values.avatar ? await base64ToFile(values.avatar) : null,
|
||||||
|
//
|
||||||
name: values.name,
|
name: values.name,
|
||||||
email: values.email,
|
email: values.email,
|
||||||
phone: values.phone,
|
phone: values.phone,
|
||||||
company: values.company,
|
company: values.company,
|
||||||
billingAddress: values.billingAddress,
|
//
|
||||||
taxId: values.taxId,
|
// billingAddress: values.billingAddress,
|
||||||
|
//
|
||||||
timezone: values.timezone,
|
timezone: values.timezone,
|
||||||
language: values.language,
|
language: values.language,
|
||||||
currency: values.currency,
|
currency: values.currency,
|
||||||
avatar: values.avatar ? await base64ToFile(values.avatar) : null,
|
taxId: values.taxId,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await pb.collection(COL_CUSTOMERS).update(customerId, updateData);
|
// await pb.collection(COL_USER_METAS).update(studentId, updateData);
|
||||||
toast.success('Customer updated successfully');
|
await UpdateStudentById(studentId, updateData);
|
||||||
|
toast.success('Student updated successfully');
|
||||||
router.push(paths.dashboard.students.list);
|
router.push(paths.dashboard.students.list);
|
||||||
|
|
||||||
|
if (billingAddressId) {
|
||||||
|
await UpdateBillingAddressById(billingAddressId, values.billingAddress);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
toast.error('Failed to update customer');
|
toast.error('Failed to update student');
|
||||||
} finally {
|
} finally {
|
||||||
setIsUpdating(false);
|
setIsUpdating(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[customerId, router]
|
[studentId, router]
|
||||||
);
|
);
|
||||||
|
|
||||||
const avatarInputRef = React.useRef<HTMLInputElement>(null);
|
const avatarInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
@@ -162,13 +176,14 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
setShowLoading(true);
|
setShowLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await pb.collection(COL_CUSTOMERS).getOne(id);
|
const result = await getStudentById(id);
|
||||||
reset({ ...defaultValues, ...result });
|
reset({ ...defaultValues, ...result });
|
||||||
console.log({ result });
|
|
||||||
|
|
||||||
if (result.avatar_file) {
|
setBillingAddressId(result.billingAddress.id);
|
||||||
|
|
||||||
|
if (result.avatar) {
|
||||||
const fetchResult = await fetch(
|
const fetchResult = await fetch(
|
||||||
`http://127.0.0.1:8090/api/files/${result.collectionId}/${result.id}/${result.avatar_file}`
|
`http://127.0.0.1:8090/api/files/${result.collectionId}/${result.id}/${result.avatar}`
|
||||||
);
|
);
|
||||||
const blob = await fetchResult.blob();
|
const blob = await fetchResult.blob();
|
||||||
const url = await fileToBase64(blob);
|
const url = await fileToBase64(blob);
|
||||||
@@ -176,7 +191,7 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
toast.error('Failed to load customer data');
|
toast.error('Failed to load student data');
|
||||||
setShowError({ show: true, detail: JSON.stringify(error, null, 2) });
|
setShowError({ show: true, detail: JSON.stringify(error, null, 2) });
|
||||||
} finally {
|
} finally {
|
||||||
setShowLoading(false);
|
setShowLoading(false);
|
||||||
@@ -186,9 +201,9 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
void loadExistingData(customerId);
|
void loadExistingData(studentId);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [customerId]);
|
}, [studentId]);
|
||||||
|
|
||||||
if (showLoading) return <FormLoading />;
|
if (showLoading) return <FormLoading />;
|
||||||
if (showError.show)
|
if (showError.show)
|
||||||
@@ -299,7 +314,7 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
error={Boolean(errors.email)}
|
error={Boolean(errors.email)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>Email</InputLabel>
|
<InputLabel required>{t('edit.email-address')}</InputLabel>
|
||||||
<OutlinedInput
|
<OutlinedInput
|
||||||
{...field}
|
{...field}
|
||||||
type="email"
|
type="email"
|
||||||
@@ -321,7 +336,7 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
error={Boolean(errors.phone)}
|
error={Boolean(errors.phone)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>Phone</InputLabel>
|
<InputLabel required>{t('edit.phone-number')}</InputLabel>
|
||||||
<OutlinedInput {...field} />
|
<OutlinedInput {...field} />
|
||||||
{errors.phone ? <FormHelperText>{errors.phone.message}</FormHelperText> : null}
|
{errors.phone ? <FormHelperText>{errors.phone.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -354,7 +369,7 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
</Stack>
|
</Stack>
|
||||||
{/* */}
|
{/* */}
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Typography variant="h6">Billing Information</Typography>
|
<Typography variant="h6">{t('edit.billing-information')}</Typography>
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
spacing={3}
|
spacing={3}
|
||||||
@@ -373,9 +388,12 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
>
|
>
|
||||||
<InputLabel required>Country</InputLabel>
|
<InputLabel required>Country</InputLabel>
|
||||||
<Select {...field}>
|
<Select {...field}>
|
||||||
|
<MenuItem value="">No Country selected</MenuItem>
|
||||||
<MenuItem value="US">United States</MenuItem>
|
<MenuItem value="US">United States</MenuItem>
|
||||||
<MenuItem value="UK">United Kingdom</MenuItem>
|
<MenuItem value="UK">United Kingdom</MenuItem>
|
||||||
<MenuItem value="CA">Canada</MenuItem>
|
<MenuItem value="CA">Canada</MenuItem>
|
||||||
|
<MenuItem value="DE">Germany</MenuItem>
|
||||||
|
<MenuItem value="ES">Spain</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
{errors.billingAddress?.country ? (
|
{errors.billingAddress?.country ? (
|
||||||
<FormHelperText>{errors.billingAddress.country.message}</FormHelperText>
|
<FormHelperText>{errors.billingAddress.country.message}</FormHelperText>
|
||||||
@@ -438,7 +456,7 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
error={Boolean(errors.billingAddress?.zipCode)}
|
error={Boolean(errors.billingAddress?.zipCode)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>Zip Code</InputLabel>
|
<InputLabel required>{t('edit.zip-code')}</InputLabel>
|
||||||
<OutlinedInput {...field} />
|
<OutlinedInput {...field} />
|
||||||
{errors.billingAddress?.zipCode ? (
|
{errors.billingAddress?.zipCode ? (
|
||||||
<FormHelperText>{errors.billingAddress.zipCode.message}</FormHelperText>
|
<FormHelperText>{errors.billingAddress.zipCode.message}</FormHelperText>
|
||||||
@@ -459,7 +477,7 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
error={Boolean(errors.billingAddress?.line1)}
|
error={Boolean(errors.billingAddress?.line1)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>Address Line 1</InputLabel>
|
<InputLabel required>{t('edit.address-line-1')}</InputLabel>
|
||||||
<OutlinedInput {...field} />
|
<OutlinedInput {...field} />
|
||||||
{errors.billingAddress?.line1 ? (
|
{errors.billingAddress?.line1 ? (
|
||||||
<FormHelperText>{errors.billingAddress.line1.message}</FormHelperText>
|
<FormHelperText>{errors.billingAddress.line1.message}</FormHelperText>
|
||||||
@@ -494,7 +512,7 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Typography variant="h6">Additional Information</Typography>
|
<Typography variant="h6">{t('edit.additional-information')}</Typography>
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
spacing={3}
|
spacing={3}
|
||||||
@@ -541,8 +559,10 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
>
|
>
|
||||||
<InputLabel required>Language</InputLabel>
|
<InputLabel required>Language</InputLabel>
|
||||||
<Select {...field}>
|
<Select {...field}>
|
||||||
|
<MenuItem value="">no language selected</MenuItem>
|
||||||
<MenuItem value="en">English</MenuItem>
|
<MenuItem value="en">English</MenuItem>
|
||||||
<MenuItem value="es">Spanish</MenuItem>
|
<MenuItem value="es">Spanish</MenuItem>
|
||||||
|
<MenuItem value="de">German</MenuItem>
|
||||||
<MenuItem value="fr">French</MenuItem>
|
<MenuItem value="fr">French</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
{errors.language ? <FormHelperText>{errors.language.message}</FormHelperText> : null}
|
{errors.language ? <FormHelperText>{errors.language.message}</FormHelperText> : null}
|
||||||
@@ -562,8 +582,9 @@ export function StudentEditForm(): React.JSX.Element {
|
|||||||
error={Boolean(errors.currency)}
|
error={Boolean(errors.currency)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>Currency</InputLabel>
|
<InputLabel required>{t('edit.currency')}</InputLabel>
|
||||||
<Select {...field}>
|
<Select {...field}>
|
||||||
|
<MenuItem value="">no currency selected</MenuItem>
|
||||||
<MenuItem value="USD">USD</MenuItem>
|
<MenuItem value="USD">USD</MenuItem>
|
||||||
<MenuItem value="EUR">EUR</MenuItem>
|
<MenuItem value="EUR">EUR</MenuItem>
|
||||||
<MenuItem value="GBP">GBP</MenuItem>
|
<MenuItem value="GBP">GBP</MenuItem>
|
||||||
|
@@ -23,21 +23,23 @@ export interface CreateFormProps {
|
|||||||
email: string;
|
email: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
company?: string;
|
company?: string;
|
||||||
billingAddress?: {
|
// handle seperately
|
||||||
country: string;
|
// billingAddress?: {
|
||||||
state: string;
|
// country: string;
|
||||||
city: string;
|
// state: string;
|
||||||
zipCode: string;
|
// city: string;
|
||||||
line1: string;
|
// zipCode: string;
|
||||||
line2?: string;
|
// line1: string;
|
||||||
};
|
// line2?: string;
|
||||||
|
// };
|
||||||
taxId?: string;
|
taxId?: string;
|
||||||
timezone: string;
|
timezone: string;
|
||||||
language: string;
|
language: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
avatar?: string;
|
avatar?: File | null;
|
||||||
// quota?: number;
|
// quota?: number;
|
||||||
// status?: 'pending' | 'active' | 'blocked';
|
state?: 'pending' | 'active' | 'blocked';
|
||||||
|
meta: Record<string, null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RULES: form data structure for editing existing student
|
// RULES: form data structure for editing existing student
|
||||||
|
Reference in New Issue
Block a user