accepted items added
This commit is contained in:
@@ -34,7 +34,7 @@ import {
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import get from 'lodash.get';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Controller, useFieldArray, useForm } from 'react-hook-form';
|
||||
|
||||
const StyledCreateBox = styled(Box)`
|
||||
@@ -67,9 +67,26 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
const t = useMyTranslation();
|
||||
const params = useSearchParams();
|
||||
const { push } = useMyNavigation();
|
||||
const [partyId, setPartyId] = useState<number | string>('');
|
||||
const [partyId, setPartyId] = useState<number | string>(initialValues?.partyId || '');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const selectMenuProps = useMemo(
|
||||
() => ({
|
||||
PaperProps: { style: { maxHeight: 280 } },
|
||||
autoFocus: false,
|
||||
disableAutoFocus: true,
|
||||
disableEnforceFocus: true,
|
||||
disableRestoreFocus: true,
|
||||
disableScrollLock: true,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Barcha box/packetlarni barcha sahifadan yuklash uchun state
|
||||
const [allPackets, setAllPackets] = useState<any[]>([]);
|
||||
// Barcha mahsulotlarni barcha sahifadan yuklash uchun map
|
||||
const [allItemsMap, setAllItemsMap] = useState<{ [packetId: number]: any[] }>({});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
@@ -94,6 +111,65 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
},
|
||||
});
|
||||
|
||||
// Paketlar va mahsulotlarni yuklash
|
||||
const [isLoadingPackets, setIsLoadingPackets] = useState(false);
|
||||
useEffect(() => {
|
||||
const fetchAllPackets = async () => {
|
||||
if (!partyId) {
|
||||
setAllPackets([]);
|
||||
return;
|
||||
}
|
||||
setIsLoadingPackets(true);
|
||||
let packets: any[] = [];
|
||||
let totalPages = 1;
|
||||
try {
|
||||
const firstRes = await box_requests.getAll({ partyId, page: 1 });
|
||||
const firstData = firstRes?.data?.data;
|
||||
packets = firstData?.data || [];
|
||||
totalPages = firstData?.totalPages || 1;
|
||||
|
||||
if (totalPages > 1) {
|
||||
const promises = [];
|
||||
for (let page = 2; page <= totalPages; page++) {
|
||||
promises.push(box_requests.getAll({ partyId, page }));
|
||||
}
|
||||
const results = await Promise.all(promises);
|
||||
results.forEach(res => {
|
||||
const data = res?.data?.data;
|
||||
packets = [...packets, ...(data?.data || [])];
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
setAllPackets(packets);
|
||||
setIsLoadingPackets(false);
|
||||
};
|
||||
fetchAllPackets();
|
||||
}, [partyId]);
|
||||
|
||||
const fetchAllItemsForPacket = async (packetId: number) => {
|
||||
let items: any[] = [];
|
||||
let totalPages = 1;
|
||||
try {
|
||||
const firstRes = await item_requests.getAll({ packetId, page: 1 });
|
||||
const firstData = firstRes?.data?.data;
|
||||
items = firstData?.data || [];
|
||||
totalPages = firstData?.totalPages || 1;
|
||||
if (totalPages > 1) {
|
||||
const promises = [];
|
||||
for (let page = 2; page <= totalPages; page++) {
|
||||
promises.push(item_requests.getAll({ packetId, page }));
|
||||
}
|
||||
const results = await Promise.all(promises);
|
||||
results.forEach(res => {
|
||||
const data = res?.data?.data;
|
||||
items = [...items, ...(data?.data || [])];
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
setAllItemsMap(prev => ({ ...prev, [packetId]: items }));
|
||||
return items;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (initialValues) {
|
||||
reset({
|
||||
@@ -111,9 +187,15 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
if (initialValues.partyId) {
|
||||
setPartyId(initialValues.partyId);
|
||||
}
|
||||
if (initialValues.paketIds) {
|
||||
initialValues.paketIds.forEach((paket: any) => {
|
||||
fetchAllItemsForPacket(paket.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [initialValues, reset]);
|
||||
|
||||
// useFieldArray keyName="key" orqali unique key
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control,
|
||||
name: 'packetItemDtos',
|
||||
@@ -165,21 +247,16 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
}
|
||||
});
|
||||
|
||||
const handlePacketChange = (index: number, value: number) => {
|
||||
setValue(`packetItemDtos.${index}.packetId`, value);
|
||||
setValue(`packetItemDtos.${index}.itemDtos`, []);
|
||||
};
|
||||
|
||||
const appendPacket = () => {
|
||||
append({ packetId: 0, itemDtos: [] });
|
||||
setTimeout(() => {
|
||||
document.activeElement instanceof HTMLElement && document.activeElement.blur();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const removePacket = (index: number) => {
|
||||
remove(index);
|
||||
};
|
||||
const [packetSearchTerm, setPacketSearchTerm] = useState('');
|
||||
|
||||
const packetSearchInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handlePartyChange = (event: any) => {
|
||||
const selectedParty = parties.find(p => p.id === event.target.value);
|
||||
@@ -190,113 +267,76 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const [paketName, setPaketName] = useState<string>('');
|
||||
const PacketRow = ({ index, field }: { index: number; field: any }) => {
|
||||
const packetId = watch(`packetItemDtos.${index}.packetId`);
|
||||
const [itemsPage, setItemsPage] = useState(1);
|
||||
const [itemsList, setItemsList] = useState<any[]>([]);
|
||||
const [itemsHasMore, setItemsHasMore] = useState(true);
|
||||
const itemScrollRef = useRef<HTMLDivElement>(null);
|
||||
const [packetsPage, setPacketsPage] = useState(1);
|
||||
const [packetsList, setPacketsList] = useState<any[]>([]);
|
||||
const [packetsHasMore, setPacketsHasMore] = useState(true);
|
||||
const [selectedProductNames, setSelectedProductNames] = useState<{ [packetIndex: number]: { [productId: number]: string } }>({});
|
||||
const [paketName, setPaketName] = useState<string>('');
|
||||
const [lastPacketId, setLastPacketId] = useState<number | null>(null);
|
||||
|
||||
const { isLoading: isLoadingProducts } = useQuery({
|
||||
queryKey: ['product-list', packetId, itemsPage],
|
||||
queryFn: () => item_requests.getAll({ packetId, page: itemsPage }),
|
||||
enabled: !!packetId,
|
||||
onSuccess: data => {
|
||||
const newItems = data?.data?.data?.data || [];
|
||||
setItemsList(prev => (itemsPage === 1 ? newItems : [...prev, ...newItems]));
|
||||
},
|
||||
});
|
||||
|
||||
const packetScrollRef = useRef<HTMLDivElement>(null);
|
||||
const [keyword, setKeyword] = useState<string>('');
|
||||
const { isFetching: isLoadingPackets } = useQuery({
|
||||
queryKey: ['packets-list', partyId, keyword, packetsPage],
|
||||
queryFn: () => box_requests.getAll({ partyId, cargoId: keyword, page: packetsPage }),
|
||||
enabled: !!partyId,
|
||||
onSuccess: data => {
|
||||
const newPackets = data?.data?.data?.data || [];
|
||||
setPacketsList(prev => (packetsPage === 1 ? newPackets : [...prev, ...newPackets]));
|
||||
const totalPages = data?.data?.data?.totalPages || 0;
|
||||
setPacketsHasMore(packetsPage < totalPages);
|
||||
},
|
||||
});
|
||||
const [selectedProductNames, setSelectedProductNames] = useState<{ [productId: number]: string }>({});
|
||||
const packetsList = keyword ? allPackets.filter(p => (p.name || '').toLowerCase().includes(keyword.toLowerCase())) : allPackets;
|
||||
const itemsList = allItemsMap[packetId] || [];
|
||||
const loadingItems = !allItemsMap[packetId] && !!packetId;
|
||||
|
||||
const handlePacketScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
|
||||
const isNearBottom = scrollHeight - scrollTop - clientHeight < 50;
|
||||
|
||||
if (isNearBottom && !isLoadingPackets && packetsHasMore) {
|
||||
setPacketsPage(prev => prev + 1);
|
||||
useEffect(() => {
|
||||
if (packetId && !allItemsMap[packetId]) {
|
||||
fetchAllItemsForPacket(packetId);
|
||||
}
|
||||
};
|
||||
}, [packetId]);
|
||||
|
||||
const handleItemScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
|
||||
const isNearBottom = scrollHeight - scrollTop - clientHeight < 50;
|
||||
|
||||
if (isNearBottom && !isLoadingProducts && itemsHasMore) {
|
||||
setItemsPage(prev => prev + 1);
|
||||
useEffect(() => {
|
||||
if (packetId && field?.itemDtos?.length && itemsList.length) {
|
||||
const names: { [productId: number]: string } = {};
|
||||
field.itemDtos.forEach((id: number) => {
|
||||
const prod = itemsList.find(p => p.id === id);
|
||||
names[id] = prod?.name || prod?.nameRu || String(id);
|
||||
});
|
||||
setSelectedProductNames(names);
|
||||
}
|
||||
};
|
||||
}, [field?.itemDtos, itemsList, packetId]);
|
||||
|
||||
const handleProductChange = (packetIndex: number, product: any, checked: boolean) => {
|
||||
const handleProductChange = (product: any, checked: boolean) => {
|
||||
setSelectedProductNames(prev => {
|
||||
const prevNames = prev[packetIndex] || {};
|
||||
if (checked) {
|
||||
return { ...prev, [packetIndex]: { ...prevNames, [product.id]: product.name || product.nameRu || String(product.id) } };
|
||||
return { ...prev, [product.id]: product.name || product.nameRu || String(product.id) };
|
||||
} else {
|
||||
const newNames = { ...prevNames };
|
||||
const newNames = { ...prev };
|
||||
delete newNames[product.id];
|
||||
return { ...prev, [packetIndex]: newNames };
|
||||
return newNames;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const element = document.getElementById(`packet-select-${index}`);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}, [fields.length]);
|
||||
|
||||
const handleSelectAllProducts = async () => {
|
||||
if (!packetId) return;
|
||||
let allProducts: any[] = [];
|
||||
let page = 1;
|
||||
let totalPages = 1;
|
||||
try {
|
||||
do {
|
||||
const res = await item_requests.list({ packetId, page });
|
||||
const data = res?.data?.data;
|
||||
const products = data?.data || [];
|
||||
totalPages = data?.totalPages || 1;
|
||||
allProducts = [...allProducts, ...products];
|
||||
page++;
|
||||
} while (page <= totalPages);
|
||||
} catch (e) {
|
||||
// error silent
|
||||
let allProducts = allItemsMap[packetId] || [];
|
||||
if (!allProducts.length) {
|
||||
allProducts = await fetchAllItemsForPacket(packetId);
|
||||
}
|
||||
if (allProducts.length > 0) {
|
||||
setValue(
|
||||
`packetItemDtos.${index}.itemDtos`,
|
||||
allProducts.map((p: any) => p.id)
|
||||
);
|
||||
setSelectedProductNames((prev: any) => ({
|
||||
...prev,
|
||||
[index]: allProducts.reduce(
|
||||
setSelectedProductNames(
|
||||
allProducts.reduce(
|
||||
(acc, p) => ({
|
||||
...acc,
|
||||
[p.id]: p.name || p.nameRu || String(p.id),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
}));
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearAll = () => {
|
||||
setValue(`packetItemDtos.${index}.itemDtos`, []);
|
||||
setSelectedProductNames((prev: any) => ({ ...prev, [index]: {} }));
|
||||
setSelectedProductNames({});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -315,59 +355,45 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
justifyContent={'space-between'}
|
||||
alignItems={'center'}
|
||||
>
|
||||
<OutlinedInput onChange={e => setKeyword(e.target.value)} />
|
||||
<OutlinedInput onChange={e => setKeyword(e.target.value)} autoFocus={false} />
|
||||
<div style={{ width: '100%' }}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id={`packet-select-label-${index}`}>{t('packet')}</InputLabel>
|
||||
<Controller
|
||||
name={`packetItemDtos.${index}.packetId`}
|
||||
control={control}
|
||||
rules={{ required: requiredText }}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<Select
|
||||
{...field}
|
||||
labelId={`packet-select-label-${index}`}
|
||||
label={t('packet')}
|
||||
value={field.value || ''}
|
||||
disabled={isLoadingPackets}
|
||||
renderValue={selected =>
|
||||
paketName || packetsList.find(p => p.id === selected)?.name || t('select')
|
||||
}
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
style: { maxHeight: 280 },
|
||||
ref: packetScrollRef,
|
||||
onScroll: handlePacketScroll,
|
||||
},
|
||||
disableAutoFocus: true,
|
||||
autoFocus: false,
|
||||
}}
|
||||
onClick={e => e.stopPropagation()}
|
||||
onChange={e => {
|
||||
field.onChange(e);
|
||||
const selected = packetsList.find(p => p.id === e.target.value);
|
||||
setPaketName(selected?.name || '');
|
||||
}}
|
||||
>
|
||||
{isLoadingPackets && packetsList.length === 0 ? (
|
||||
<MenuItem disabled>
|
||||
<CircularProgress size={24} />
|
||||
render={({ field: selectField }) => (
|
||||
<Select
|
||||
{...selectField}
|
||||
autoFocus={false}
|
||||
labelId={`packet-select-label-${index}`}
|
||||
id={`packet-select-${index}`}
|
||||
label={t('packet')}
|
||||
MenuProps={selectMenuProps}
|
||||
onChange={e => {
|
||||
e.stopPropagation();
|
||||
selectField.onChange(e);
|
||||
}}
|
||||
value={selectField.value || ''}
|
||||
renderValue={selected => {
|
||||
const selectedPacket = packetsList.find(p => p.id === selected);
|
||||
return selectedPacket ? selectedPacket.name : t('loading');
|
||||
}}
|
||||
>
|
||||
{isLoadingPackets ? (
|
||||
<MenuItem disabled>
|
||||
<CircularProgress size={24} />
|
||||
</MenuItem>
|
||||
) : packetsList.length === 0 ? (
|
||||
<MenuItem disabled>{t('not_found') || 'Paketlar topilmadi'}</MenuItem>
|
||||
) : (
|
||||
packetsList.map(packet => (
|
||||
<MenuItem key={packet.id} value={packet.id}>
|
||||
{packet.name}
|
||||
</MenuItem>
|
||||
) : (
|
||||
packetsList.map(packet => (
|
||||
<MenuItem key={packet.id} value={packet.id} onClick={() => setPaketName(packet.name)}>
|
||||
{packet.name}
|
||||
</MenuItem>
|
||||
))
|
||||
)}
|
||||
{isLoadingPackets && packetsList.length > 0 && (
|
||||
<MenuItem disabled>
|
||||
<CircularProgress size={20} />
|
||||
</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
</>
|
||||
))
|
||||
)}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
{!!get(errors, `packetItemDtos.${index}.packetId`) && (
|
||||
@@ -388,7 +414,6 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{packetId && (
|
||||
<Box mt={2} sx={{ width: '100%' }}>
|
||||
<Box display='flex' justifyContent='space-between' alignItems='center' mb={2}>
|
||||
@@ -404,7 +429,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
{isLoadingProducts && itemsList.length === 0 ? (
|
||||
{loadingItems ? (
|
||||
<CircularProgress />
|
||||
) : (
|
||||
<FormControl fullWidth>
|
||||
@@ -415,35 +440,25 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
<Select
|
||||
{...field}
|
||||
multiple
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
style: { maxHeight: 280 },
|
||||
ref: itemScrollRef,
|
||||
onScroll: handleItemScroll,
|
||||
},
|
||||
disableAutoFocus: true,
|
||||
autoFocus: false,
|
||||
}}
|
||||
MenuProps={selectMenuProps}
|
||||
renderValue={selected => (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
{(selected as any[]).map(id => {
|
||||
return (
|
||||
<Chip
|
||||
key={id}
|
||||
label={
|
||||
selectedProductNames[index]?.[id] ||
|
||||
itemsList.find(p => p.id === id)?.name ||
|
||||
itemsList.find(p => p.id === id)?.nameRu ||
|
||||
id
|
||||
}
|
||||
onDelete={e => {
|
||||
e.stopPropagation();
|
||||
field.onChange(field.value.filter((x: any) => x !== id));
|
||||
handleProductChange(index, { id }, false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{(selected as any[]).map((id: number) => (
|
||||
<Chip
|
||||
key={id}
|
||||
label={
|
||||
selectedProductNames[id] ||
|
||||
itemsList.find(p => p.id === id)?.name ||
|
||||
itemsList.find(p => p.id === id)?.nameRu ||
|
||||
id
|
||||
}
|
||||
onDelete={e => {
|
||||
e.stopPropagation();
|
||||
field.onChange(field.value.filter((x: any) => x !== id));
|
||||
handleProductChange({ id }, false);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
@@ -461,7 +476,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
newValue = field.value.filter((x: any) => x !== product.id);
|
||||
}
|
||||
field.onChange(newValue);
|
||||
handleProductChange(index, product, checked);
|
||||
handleProductChange(product, checked);
|
||||
}}
|
||||
>
|
||||
<Checkbox checked={field.value.includes(product.id)} />
|
||||
@@ -489,11 +504,10 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
backgroundColor: '#fff',
|
||||
}}
|
||||
>
|
||||
<Box component='form' onSubmit={onSubmit}>
|
||||
<Box component='form' onSubmit={onSubmit} sx={{ overflowAnchor: 'none' }}>
|
||||
<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}>
|
||||
@@ -510,6 +524,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
{...field}
|
||||
labelId='party-select-label'
|
||||
label={t('party_name')}
|
||||
MenuProps={selectMenuProps}
|
||||
onChange={e => {
|
||||
field.onChange(e);
|
||||
handlePartyChange(e);
|
||||
@@ -534,7 +549,6 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
{!!errors.partyId && <FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>}
|
||||
</FormControl>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack
|
||||
sx={{
|
||||
@@ -552,13 +566,12 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
<PacketRow key={field.key} index={index} field={field} />
|
||||
))}
|
||||
<BaseButton variant='outlined' onClick={appendPacket} startIcon={<AddCircleRounded />} sx={{ mt: 2 }}>
|
||||
{t('add_packet')}
|
||||
{t('add_more')}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<BaseButton variant='contained' type='submit' disabled={loading} fullWidth sx={{ py: 1.5 }}>
|
||||
{loading ? <CircularProgress size={24} color='inherit' /> : editMode ? t('update_box') : t('create_box')}
|
||||
</BaseButton>
|
||||
|
||||
Reference in New Issue
Block a user