real-boxing

This commit is contained in:
Samandar Turg'unboev
2025-05-22 10:12:44 +05:00
parent d68d75a0cc
commit e7c4cda9a2
8 changed files with 258 additions and 493 deletions

View File

@@ -6,7 +6,7 @@ import { party_requests } from '@/data/party/party.requests';
import { pageLinks } from '@/helpers/constants';
import { notifyError, notifyUnknownError } from '@/services/notification';
import { Box, Divider, FormHelperText, Grid, Stack, Typography, styled } from '@mui/material';
import { useParams, useRouter, useSearchParams } from 'next/navigation';
import { useParams, useSearchParams, useRouter } from 'next/navigation';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useLocale } from 'use-intl';
import { Controller, useFieldArray, useForm } from 'react-hook-form';
@@ -21,7 +21,6 @@ import { customer_requests } from '@/data/customers/customer.requests';
import { useAuthContext } from '@/context/auth-context';
import { useMyNavigation } from '@/hooks/useMyNavigation';
import AsyncSelect from 'react-select/async';
import { Party } from '@/data/party/party.model';
import cloneDeep from 'lodash.clonedeep';
import { item_requests } from '@/data/item/item.requests';
import get from 'lodash.get';
@@ -57,20 +56,16 @@ type Props = {
box_size: string;
status: BoxStatus;
packetId: string;
passportName: string;
passportId: number;
partyId: number;
partyName: string;
clientId: number;
client_id: string;
clientName: string;
products_list: {
id: number;
price: number | string;
cargoId: string;
trekId: string;
name: string;
@@ -93,16 +88,14 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
finished: boolean;
partyFinished: boolean;
clientFinished: boolean;
settedDefaultParty: Party | null;
settedDefaultParty: any;
settedDefaultClient: Customer | null;
// settedDefaultPartyValue: { value: number; label: string }[] | null;
}>({
settedDefaultParty: null,
settedDefaultClient: null,
partyFinished: false,
clientFinished: false,
finished: false,
// settedDefaultPartyValue: partiesData?.[0] || null,
});
const {
register,
@@ -114,28 +107,30 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
} = useForm<any>({
defaultValues: {
partyId: params.get('party_id') ? +params.get('party_id')! : '',
paketIds: editMode
? [{ id: initialValues?.partyId }]
: params.get('party_id')
? [{ id: +params.get('party_id')! }]
: [{ id: '' }],
box_weight: 0.9,
box_type: 'KG',
box_size: '50x40x40',
status: 'READY_TO_INVOICE',
cargoId: initialValues?.client_id,
// passportId: {value: initialValues?.passportId},
...(editMode
? {}
: {
products_list: [
{
id: '',
cargoId: '',
trekId: '',
name: '',
nameRu: '',
amount: '',
weight: '',
price: '',
},
],
}),
products_list: editMode
? initialValues?.products_list
: [
{
id: '',
cargoId: '',
trekId: '',
name: '',
nameRu: '',
amount: '',
weight: '',
price: '',
},
],
...initialValues,
},
});
@@ -146,12 +141,17 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
name: 'products_list',
keyName: 'key',
});
const pakets = useFieldArray({
control,
name: 'paketIds',
keyName: 'key',
});
const controlledProductFields = watch('products_list');
const partyIdValue = watch('partyId');
const clientIdValue = watch('client_id');
const cargoId = watch('cargoId');
const requiredText = t('required');
const [selectedPassport, setSelectedPassport] = useState<Passport | null>(null); // Tanlangan passportni saqlash uchun state (tipi Passport yoki null)
const [selectedPassport, setSelectedPassport] = useState<Passport | null>(null);
const passportOptionsInitial = initialValues?.passportId &&
initialValues?.passportName && [
@@ -160,20 +160,16 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
label: initialValues?.passportName,
},
];
const n = "123ds"
n.toUpperCase()
const { data: passportOptions } = useRequest(() => passport_requests.getAll({ cargoId: cargoId?.toUpperCase() }), {
enabled: !!cargoId,
selectData: data => {
// Ma'lumotlarni BaseReactSelect uchun mos formatga o'tkazish
const passportOptions = data.data.data.map((passport: Passport) => ({
// data.data endi Passport[]
value: passport.id, // passport id sini value sifatida
label: passport.fullName, // fullName ni label sifatida
value: passport.id,
label: passport.fullName,
}));
const passportId = watch('passportId');
if (!passportId && initialValues?.passportId && cargoId === initialValues?.client_id) {
const currentOption = passportOptions?.find((item: { value: number }) => item.value === initialValues?.passportId);
setValue('passportId', currentOption);
@@ -182,14 +178,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
return passportOptions;
},
dependencies: [cargoId],
placeholderData: [], // Kerak emas, chunki server PageAble qaytarmayapti
onSuccess(data) {
// if (data?.data.data?.[0]?.id) {
// setValue("passportId", initialValues?.passportId)
// setValue('passport_id', data.data.data[0].id);
// setSelectedPassport(data.data.data[0]); // Birinchi elementni tanlash
// }
},
placeholderData: [],
});
useEffect(() => {
@@ -205,6 +194,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
if (!editMode && data?.data?.data?.data?.[0]) {
helperRef.current.settedDefaultParty = data.data.data.data[0];
setValue('partyId', data.data.data.data[0].id);
setValue('paketIds[0].id', data.data.data.data[0].id);
}
helperRef.current.partyFinished = true;
if (helperRef.current.clientFinished) {
@@ -223,7 +213,6 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
enabled: !!partyIdValue,
selectData(data) {
return data.data.data.data.map(p => ({ value: p.aviaCargoId, label: p.fullName }));
// return data.data.data.map(p => ({ value: p.id, label: p.fullName }));
},
dependencies: [partyIdValue],
onSuccess(data) {
@@ -241,7 +230,6 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
);
const onPassportChange = (newValue: Passport | null) => {
// Tipi Passport | null
setSelectedPassport(newValue);
if (newValue) {
setValue('passport_id', newValue.id || null);
@@ -251,17 +239,15 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
};
const onSubmit = handleSubmit(async values => {
try {
setLoading(true);
if (editMode) {
const updateBody: UpdateBoxBodyType = {
cargoId: values.client_id,
passportId: values.passportId.value,
passportId: values.passportId?.value,
status: values.status,
packetId: initialValues?.packetId,
packetId: values.paketIds.map((p: any) => p.id), // Bir nechta paketId
items: values.products_list.map((item: any) => {
const _price = +item.price ? +item.price : 0;
const _amount = +item.amount ? +item.amount : 0;
@@ -269,9 +255,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
return {
id: item.id,
// cargoId: item.cargoId,
trekId: item.trekId,
// name: item.name + (item.nameRu ? ` / ${item.nameRu}` : ''),
name: item.name,
nameRu: item?.nameRu,
weight: +item.weight,
@@ -300,7 +284,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
const createBody: CreateBoxBodyType = {
status: values.status,
cargoId: values.cargoId,
passportId: values.passportId.value,
passportId: values.passportId?.value,
partyId: values.partyId,
items: values.products_list.map((item: any) => {
return {
@@ -328,11 +312,13 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
});
};
// const clientOptions = (inputValue: string) => {
// return customer_requests.getAll({ clientName: inputValue, page: 1 }).then(res => {
// return res.data.data.data.map(p => ({ label: p.fullName, value: p.id }));
// });
// };
const appendPaket = () => {
pakets.append({ id: '' });
};
const removePaket = (index: number) => {
pakets.remove(index);
};
const appendProduct = () => {
products.append({
@@ -355,7 +341,6 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
if (!text) return;
try {
// const responseText = await box_requests.translateWithGoogleApi({ text });
const responseText = await box_requests.translateWithMemoryApi({ text });
setValue(`products_list.${index}.nameRu`, responseText || '');
} catch (error) {
@@ -380,11 +365,11 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
label: string;
value: BoxStatus;
}[] = [
{
label: t('READY_TO_INVOICE'),
value: 'READY_TO_INVOICE',
},
];
{
label: t('READY_TO_INVOICE'),
value: 'READY_TO_INVOICE',
},
];
if (isAdmin) {
p.push({
@@ -408,11 +393,11 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
>
<Box component={'form'} onSubmit={onSubmit}>
<Typography variant='h5' mb={3.5}>
{editMode ? t('update_packet') : t('create_packet')}
{editMode ? t('update_box') : t('create_box')}
</Typography>
<Grid container columnSpacing={2.5} rowSpacing={3} mb={3.5}>
<Grid item xs={5}>
<Grid item xs={12}>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('party_name')}
</Typography>
@@ -429,15 +414,15 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
defaultValue={
editMode
? {
value: initialValues.partyId,
label: initialValues.partyName,
}
value: initialValues.partyId,
label: initialValues.partyName,
}
: partiesData?.length
? {
? {
value: partiesData[0].value,
label: partiesData[0].label,
}
: null
: null
}
styles={selectDefaultStyles}
noOptionsMessage={() => t('not_found')}
@@ -451,88 +436,9 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
);
}}
/>
{/* @ts-expect-error */}
{!!errors.partyId?.message && <FormHelperText sx={{ color: 'red' }}>{errors.partyId?.message}</FormHelperText>}
</Grid>
<Grid item xs={5}>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('status')}
</Typography>
<Controller
name='status'
control={control}
rules={{ required: requiredText }}
render={({ field, fieldState, formState }) => {
return (
<BaseReactSelect
value={boxStatuses?.find(p => p.value === field.value)}
onChange={(newValue: any) => {
field.onChange(newValue.value);
}}
onBlur={field.onBlur}
name={field.name}
options={boxStatuses}
/>
);
}}
/>
{/* @ts-expect-error */}
{!!errors.box_type?.message && <FormHelperText sx={{ color: 'red' }}>{errors.box_type?.message}</FormHelperText>}
</Grid>
<Grid item xs={5}>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('cargo_id')}
</Typography>
<BaseInput
disabled={!!initialValues}
type='text'
fullWidth
inputProps={{
step: 0.1,
}}
mainBorderColor='#D8D8D8'
placeholder={t('cargo_id')}
{...register('cargoId')}
/>
{!!errors.net_weight?.message && (
// @ts-expect-error
<FormHelperText sx={{ color: 'red' }}>{errors.net_weight?.message}</FormHelperText>
)}
</Grid>
<Grid item xs={5}>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('passport')}
</Typography>
<Controller
name='passportId'
control={control}
rules={{ required: requiredText }}
render={({ field, fieldState, formState }) => {
return (
<BaseReactSelect
// value={selectedPassport}
// onChange={onPassportChange}
// value={field.value}
{...field}
onChange={(newValue: any) => {
onPassportChange(newValue);
field.onChange(newValue);
}}
// options={passportOptions || passportOptionsInitial || []}
options={passportOptions || passportOptionsInitial || []}
// isLoading={passportLoading}
placeholder={t('passport')}
isDisabled={!clientIdValue || !!initialValues}
noOptionsMessage={() => t('not_found')}
loadingMessage={() => t('loading')}
/>
);
}}
/>
{/* {!!errors.partyId?.message && (
<FormHelperText sx={{ color: 'red' }}>{errors.partyId?.message}</FormHelperText>
)} */}
</Grid>
<Grid item xs={12}>
@@ -544,196 +450,69 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
padding: '24px',
}}
>
{controlledProductFields.map((product: any, index: number) => {
//
//
let totalPrice = 0;
try {
const p = +product.price * +product.amount;
if (!Number.isNaN(p)) {
totalPrice = p;
}
} catch (error) {}
return (
<Box key={product.key} mb={1.5}>
<Box className='item-row' mb={1.5}>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('track_id')}
</Typography>
<BaseInput
InputProps={{
startAdornment: (
<Box
sx={{
backgroundColor: '#EBEFF5',
color: '#929191',
alignSelf: 'stretch',
height: 'auto',
display: 'flex',
alignItems: 'center',
pl: '10px',
pr: '10px',
borderRadius: '8px 0 0 8px',
}}
>
<span>ID</span>
</Box>
),
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('packet')}
</Typography>
{pakets.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, fieldState }) => (
<AsyncSelect
onChange={(newValue: any) => {
paketField.onChange(newValue?.value);
}}
fullWidth
placeholder={t('id')}
sx={{
'.MuiInputBase-root': {
paddingLeft: 0,
},
}}
{...register(`products_list.${index}.trekId`, { required: requiredText })}
defaultValue={
editMode && index === 0
? {
value: initialValues.partyId,
label: initialValues.partyName,
}
: partiesData?.length && index === 0
? {
value: partiesData[0].value,
label: partiesData[0].label,
}
: null
}
styles={selectDefaultStyles}
noOptionsMessage={() => t('not_found')}
loadingMessage={() => t('loading')}
onBlur={paketField.onBlur}
name={paketField.name}
defaultOptions={defaultParties!}
loadOptions={partyOptions}
placeholder={t('enter_party_name_to_find')}
/>
{!!get(errors, `products_list.${index}.trekId`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('name')}
</Typography>
<BaseInput
fullWidth
mainBorderColor='#D8D8D8'
placeholder={t('name')}
{...register(`products_list.${index}.name`, { required: requiredText })}
onBlur={event => {
translateAndUpdateRussianName(event.target.value, index);
}}
/>
{!!get(errors, `products_list.${index}.name`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{'NAME_RU'}
</Typography>
<BaseInput
// disabled
fullWidth
mainBorderColor='#D8D8D8'
placeholder={t('name')}
{...register(`products_list.${index}.nameRu`)}
/>
{!!get(errors, `products_list.${index}.name`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('quantity')}
</Typography>
<BaseInput
fullWidth
type='number'
mainBorderColor='#D8D8D8'
placeholder={t('quantity')}
{...register(`products_list.${index}.amount`, { required: requiredText })}
/>
{!!get(errors, `products_list.${index}.amount`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('weight')}
</Typography>
<BaseInput
fullWidth
type='number'
inputProps={{ step: 'any', min: 0, type: 'number' }}
mainBorderColor='#D8D8D8'
placeholder={t('weight')}
{...register(`products_list.${index}.weight`, { required: requiredText })}
/>
{!!get(errors, `products_list.${index}.amount`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
{isAdmin && (
<React.Fragment>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('weight')}
</Typography>
<Stack direction={'row'} alignItems={'center'} spacing={2.5}>
<BaseInput
fullWidth
type='text'
inputProps={{
step: 0.1,
}}
mainBorderColor='#D8D8D8'
placeholder={t('weight')}
{...register(`products_list.${index}.weight`)}
/>
</Stack>
</Box>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('price')}
</Typography>
<BaseInput
fullWidth
type='text'
inputProps={{
step: 0.1,
}}
mainBorderColor='#D8D8D8'
placeholder={t('price')}
{...register(`products_list.${index}.price`, { required: requiredText })}
/>
{!!get(errors, `products_list.${index}.price`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('total_price')}
</Typography>
<BaseInput
fullWidth
type='number'
inputProps={{
step: 0.001,
}}
value={totalPrice}
mainBorderColor='#D8D8D8'
placeholder={t('total_price')}
// {...register(`products_list.${index}.totalPrice`, { required: requiredText })}
/>
</Box>
</React.Fragment>
)}
<Box className='item-row-field'>
<BaseIconButton
size='small'
colorVariant='icon-error'
sx={{ flexShrink: 0, height: 'auto', marginTop: 4.5 }}
onClick={() => removeProduct(index)}
>
<Close />
</BaseIconButton>
</Box>
</Box>
<Divider color='#EBEFF6' />
/>
{!!get(errors, `paketIds.${index}.id`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
);
})}
<Stack alignItems={'center'}>
<BaseButton sx={{ backgroundColor: '#239D5F' }} startIcon={<AddCircleRounded />} onClick={appendProduct}>
{pakets.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>
@@ -749,4 +528,4 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
);
};
export default DashboardCreateRealBoxPage;
export default DashboardCreateRealBoxPage;