Files
cpost-front/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx
Samandar Turg'unboev ce2b8f3a62 realbox edit components
2025-05-24 23:02:52 +05:00

368 lines
16 KiB
TypeScript

'use client';
import BaseButton from '@/components/ui-kit/BaseButton';
import BaseIconButton from '@/components/ui-kit/BaseIconButton';
import { party_requests } from '@/data/party/party.requests';
import { box_requests } from '@/data/box/box.requests';
import { real_box_requests } from '@/data/real-box/real-box.requests';
import { pageLinks } from '@/helpers/constants';
import { notifyUnknownError } from '@/services/notification';
import { Box, FormHelperText, Grid, Stack, Typography, styled } from '@mui/material';
import { AddCircleRounded, Close } from '@mui/icons-material';
import { useSearchParams } from 'next/navigation';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Controller, useFieldArray, useForm } from 'react-hook-form';
import AsyncSelect from 'react-select/async';
import { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect';
import { useAuthContext } from '@/context/auth-context';
import { useMyNavigation } from '@/hooks/useMyNavigation';
import { useMyTranslation } from '@/hooks/useMyTranslation';
import { FormValues, RealCreateBoxBodyType, UpdateRealBoxBodyType } from '@/data/real-box/real-box.model';
import get from 'lodash.get';
import useRequest from '@/hooks/useRequest';
const StyledCreateBox = styled(Box)`
.item-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
& > * {
flex: 1 1 1;
}
`;
interface Props {
partiesData?: { value: number; label: string }[];
initialValues?: {
id?: number;
boxId?: string;
partyId?: number;
partyName?: string;
paketIds?: Array<{ id: number; packetName: string }>;
};
}
const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
const { user, isAdmin: isAdminUser } = useAuthContext();
const editMode = !!initialValues?.id;
const t = useMyTranslation();
const params = useSearchParams();
const { push } = useMyNavigation();
const [partyId, setPartyId] = useState<number | string>('');
const [loading, setLoading] = useState(false);
const helperRef = useRef<{
finished: boolean;
partyFinished: boolean;
clientFinished: boolean;
settedDefaultParty: any;
}>({
settedDefaultParty: null,
partyFinished: false,
clientFinished: false,
finished: false,
});
const {
register,
control,
handleSubmit,
setValue,
reset,
formState: { errors },
} = useForm<FormValues>({
defaultValues: {
partyName: initialValues?.partyName || '',
paketIds: initialValues?.paketIds
? initialValues.paketIds.map((paket) => ({ id: paket.id }))
: params.get('party_id')
? [{ id: +params.get('party_id')! }]
: [{ id: '' }],
id: initialValues?.id,
boxId: initialValues?.boxId,
partyId: initialValues?.partyId,
},
});
// Reset form when initialValues change (for edit mode)
useEffect(() => {
if (initialValues) {
reset({
partyName: initialValues.partyName || '',
paketIds: initialValues.paketIds
? initialValues.paketIds.map((paket) => ({ id: paket.id }))
: [{ id: '' }],
id: initialValues.id,
boxId: initialValues.boxId,
partyId: initialValues.partyId,
});
if (initialValues.partyId) {
setPartyId(initialValues.partyId);
}
}
}, [initialValues, reset]);
const { fields, append, remove } = useFieldArray({
control,
name: 'paketIds',
keyName: 'key',
});
const requiredText = t('required');
const getBoxesQuery = useRequest(
() =>
box_requests.getAll({
partyId: partyId,
}),
{
selectData: (data) => data?.data?.data ?? [],
enabled: !!partyId,
},
);
const list = useMemo(() => {
return getBoxesQuery.data?.data.filter((box: any) => box.status === 'READY_TO_INVOICE') ?? [];
}, [getBoxesQuery.data]);
useEffect(() => {
if (partyId) {
getBoxesQuery.refetch();
}
}, [partyId, getBoxesQuery.refetch]);
const { data: defaultParties } = useRequest(
() => party_requests.getAll({ status: 'COLLECTING' }),
{
enabled: true,
selectData: (data) =>
data.data.data.data.map((p: any) => ({ value: p.id, label: p.name })),
onSuccess: (data) => {
if (!editMode && data?.data?.data?.data?.[0]) {
const defaultParty = data.data.data.data[0];
helperRef.current.settedDefaultParty = defaultParty;
setValue('partyName', defaultParty.name);
setValue('paketIds.0.id', defaultParty.id); // Use dot notation
setPartyId(defaultParty.id);
}
helperRef.current.partyFinished = true;
if (helperRef.current.clientFinished) {
helperRef.current.finished = true;
}
},
placeholderData: [],
},
);
const onSubmit = handleSubmit(async (values) => {
try {
setLoading(true);
const packetDtos = values.paketIds.map((paket) => Number(paket.id)).filter((id) => id);
if (editMode) {
const updateBody: UpdateRealBoxBodyType = {
boxId: initialValues!.boxId!,
partyName: values.partyName,
packetDtos,
};
await real_box_requests.update(updateBody);
} else {
const createBody: RealCreateBoxBodyType = {
partyName: values.partyName,
packetDtos,
};
await real_box_requests.create(createBody);
}
push(pageLinks.dashboard.real_boxes.index);
} catch (error) {
notifyUnknownError(error);
} finally {
setLoading(false);
}
});
const partyOptions = async (inputValue: string) => {
try {
const res = await party_requests.getAll({
status: 'COLLECTING',
partyName: inputValue,
});
return res.data.data.data.map((p: any) => ({ label: p.name, value: p.id }));
} catch (error) {
notifyUnknownError(error);
return [];
}
};
const appendPaket = () => {
append({ id: '' });
};
const removePaket = (index: number) => {
remove(index);
};
return (
<StyledCreateBox
width={1}
mb={3}
sx={{
padding: '28px',
borderRadius: '16px',
backgroundColor: '#fff',
}}
>
<Box component="form" onSubmit={onSubmit}>
<Typography variant="h5" mb={3.5}>
{editMode ? t('update_box') : t('create_box')}
</Typography>
<Grid container columnSpacing={2.5} rowSpacing={3} mb={3.5}>
<Grid item xs={12}>
<Typography fontSize="18px" fontWeight={500} color="#5D5850" mb={2}>
{t('party_name')}
</Typography>
<Controller
name="partyName"
control={control}
rules={{ required: requiredText }}
render={({ field }) => (
<AsyncSelect
onChange={(newValue: any) => {
field.onChange(newValue?.label ?? '');
setPartyId(newValue?.value ?? '');
}}
defaultValue={
editMode
? {
value: initialValues?.partyId,
label: initialValues?.partyName,
}
: partiesData?.length
? {
value: partiesData[0].value,
label: partiesData[0].label,
}
: null
}
styles={selectDefaultStyles}
noOptionsMessage={() => t('not_found')}
loadingMessage={() => t('loading')}
onBlur={field.onBlur}
name={field.name}
defaultOptions={defaultParties ?? []}
loadOptions={partyOptions}
placeholder={t('enter_party_name_to_find')}
/>
)}
/>
{!!errors.partyName && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Grid>
<Grid item xs={12}>
<Stack
sx={{
borderRadius: '8px',
border: '2px solid #3489E4',
background: '#FFF',
padding: '24px',
}}
>
<Typography fontSize="18px" fontWeight={500} color="#5D5850" mb={2}>
{t('packet')}
</Typography>
{fields.map((field, index) => (
<Box key={field.key} className="item-row" mb={2}>
<Box className="item-row-field" flex={1}>
<Controller
name={`paketIds.${index}.id`}
control={control}
rules={{ required: requiredText }}
render={({ field: paketField }) => (
<AsyncSelect
onChange={(newValue: any) => {
paketField.onChange(newValue?.value ?? '');
}}
defaultValue={
editMode && initialValues?.paketIds?.[index]
? {
value: initialValues.paketIds[index].id,
label:
initialValues.paketIds[index].packetName ||
`Box ${initialValues.paketIds[index].id}`,
}
: null
}
styles={selectDefaultStyles}
noOptionsMessage={() => t('not_found')}
loadingMessage={() => t('loading')}
onBlur={paketField.onBlur}
name={paketField.name}
defaultOptions={list.map((box: any) => ({
value: box.id,
label: box.box_name || box.name || `Box ${box.id}`,
}))}
loadOptions={async (inputValue: string) => {
if (!partyId) return [];
try {
const res = await box_requests.getAll({
partyId,
});
return res.data.data.data.map((box: any) => ({
label: box.box_name || box.name || `Box ${box.id}`,
value: box.id,
}));
} catch (error) {
notifyUnknownError(error);
return [];
}
}}
placeholder={t('enter_box_name_to_find')}
/>
)}
/>
{!!get(errors, `paketIds.${index}.id`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
{fields.length > 1 && (
<Box className="item-row-field">
<BaseIconButton
size="small"
colorVariant="icon-error"
sx={{ flexShrink: 0, height: 'auto', marginTop: 1 }}
onClick={() => removePaket(index)}
>
<Close />
</BaseIconButton>
</Box>
)}
</Box>
))}
<Stack alignItems="center" mt={2}>
<BaseButton
sx={{ backgroundColor: '#239D5F' }}
startIcon={<AddCircleRounded />}
onClick={appendPaket}
>
{t('add_more')}
</BaseButton>
</Stack>
</Stack>
</Grid>
</Grid>
<BaseButton type="submit" colorVariant="blue" loading={loading}>
{editMode ? t('update') : t('create')}
</BaseButton>
</Box>
</StyledCreateBox>
);
};
export default DashboardCreateRealBoxPage;