add boxes

This commit is contained in:
Samandar Turg'unboev
2025-06-20 14:21:40 +05:00
parent 4c5df8007c
commit 75dea29b7b
2 changed files with 163 additions and 106 deletions

View File

@@ -30,7 +30,7 @@ export const item_requests = {
return request.get<CommonResponseType<Product[]>>('/items/find', { params }); return request.get<CommonResponseType<Product[]>>('/items/find', { params });
}, },
async list(params: { packetId?: number | string; page?: number | string }) { async list(params: { packetId?: number | string; page?: number | string }) {
return request.get<CommonResponseType<{ data: Product[] }>>('/items/list', { params }); return request.get<CommonResponseType<PageAble<{ data: Product[] }>>>('/items/list', { params });
}, },
async delete(params: { itemId: number | string }) { async delete(params: { itemId: number | string }) {
return request.delete<CommonResponseType>('/items/delete', { params }); return request.delete<CommonResponseType>('/items/delete', { params });

View File

@@ -24,6 +24,7 @@ import {
InputLabel, InputLabel,
ListItemText, ListItemText,
MenuItem, MenuItem,
OutlinedInput,
Select, Select,
Stack, Stack,
styled, styled,
@@ -62,40 +63,12 @@ interface Props {
const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
const { user } = useAuthContext(); const { user } = useAuthContext();
const editMode = !!initialValues?.id; const editMode = !!initialValues?.id;
const [keyword, setKeyword] = useState('');
const t = useMyTranslation(); const t = useMyTranslation();
const params = useSearchParams(); const params = useSearchParams();
const { push } = useMyNavigation(); const { push } = useMyNavigation();
const [partyId, setPartyId] = useState<number | string>(''); const [partyId, setPartyId] = useState<number | string>('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
// Packetlar uchun scroll state
const [packetsPage, setPacketsPage] = useState(1);
const [packetsList, setPacketsList] = useState<any[]>([]);
const [packetsHasMore, setPacketsHasMore] = useState(true);
const packetScrollRef = useRef<HTMLDivElement>(null);
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 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);
}
};
const { const {
control, control,
handleSubmit, handleSubmit,
@@ -191,19 +164,6 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
} }
}); });
const handlePartyChange = (event: any) => {
const selectedParty = parties.find(p => p.id === event.target.value);
if (selectedParty) {
setValue('partyName', selectedParty.name);
setPartyId(selectedParty.id);
setKeyword('');
setPacketsPage(1);
setPacketsList([]);
setPacketsHasMore(true);
setValue('packetItemDtos', [{ packetId: 0, itemDtos: [] }]);
}
};
const handlePacketChange = (index: number, value: number) => { const handlePacketChange = (index: number, value: number) => {
setValue(`packetItemDtos.${index}.packetId`, value); setValue(`packetItemDtos.${index}.packetId`, value);
setValue(`packetItemDtos.${index}.itemDtos`, []); setValue(`packetItemDtos.${index}.itemDtos`, []);
@@ -216,15 +176,32 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
const removePacket = (index: number) => { const removePacket = (index: number) => {
remove(index); 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);
if (selectedParty) {
setValue('partyName', selectedParty.name);
setPartyId(selectedParty.id);
setValue('packetItemDtos', [{ packetId: 0, itemDtos: [] }]);
}
};
const [paketName, setPaketName] = useState<string>('');
const PacketRow = ({ index, field }: { index: number; field: any }) => { const PacketRow = ({ index, field }: { index: number; field: any }) => {
const packetId = watch(`packetItemDtos.${index}.packetId`); const packetId = watch(`packetItemDtos.${index}.packetId`);
// Tanlangan paketning name'ini olish
// Itemlar uchun scroll state
const [itemsPage, setItemsPage] = useState(1); const [itemsPage, setItemsPage] = useState(1);
const [itemsList, setItemsList] = useState<any[]>([]); const [itemsList, setItemsList] = useState<any[]>([]);
const [itemsHasMore, setItemsHasMore] = useState(true); const [itemsHasMore, setItemsHasMore] = useState(true);
const itemScrollRef = useRef<HTMLDivElement>(null); const itemScrollRef = useRef<HTMLDivElement>(null);
// Packetlar uchun scroll state
const [packetsPage, setPacketsPage] = useState(1);
const [packetsList, setPacketsList] = useState<any[]>([]);
const selectedPacket = packetsList.find(p => p.id === packetId);
const [packetsHasMore, setPacketsHasMore] = useState(true);
const { isLoading: isLoadingProducts } = useQuery({ const { isLoading: isLoadingProducts } = useQuery({
queryKey: ['product-list', packetId, itemsPage], queryKey: ['product-list', packetId, itemsPage],
@@ -233,11 +210,33 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
onSuccess: data => { onSuccess: data => {
const newItems = data?.data?.data?.data || []; const newItems = data?.data?.data?.data || [];
setItemsList(prev => (itemsPage === 1 ? newItems : [...prev, ...newItems])); setItemsList(prev => (itemsPage === 1 ? newItems : [...prev, ...newItems]));
// const totalPages = data?.data?.data || 0;
// setItemsHasMore(itemsPage < totalPages);
}, },
}); });
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 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);
}
};
const handleItemScroll = (e: React.UIEvent<HTMLDivElement>) => { const handleItemScroll = (e: React.UIEvent<HTMLDivElement>) => {
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
const isNearBottom = scrollHeight - scrollTop - clientHeight < 50; const isNearBottom = scrollHeight - scrollTop - clientHeight < 50;
@@ -248,22 +247,51 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
}; };
useEffect(() => { useEffect(() => {
// Reset items list when packet changes
setItemsPage(1); setItemsPage(1);
setItemsList([]); setItemsList([]);
setItemsHasMore(true); setItemsHasMore(true);
}, [packetId]); }, [packetId]);
const selectedProducts = watch(`packetItemDtos.${index}.itemDtos`) || []; const [selectedProductNames, setSelectedProductNames] = useState<{ [packetIndex: number]: { [productId: number]: string } }>({});
const handleSelectAll = (checked: boolean) => {
if (checked) { const handleSelectAll = (checked: boolean, packetIndex: number, packetId: number, itemsList: any[]) => {
setValue( setSelectedProductNames(prev => {
`packetItemDtos.${index}.itemDtos`, const prevNames = prev[packetIndex] || {};
itemsList.map((p: any) => p.id) if (checked) {
); // add all products of current page
} else { const newNames = { ...prevNames };
setValue(`packetItemDtos.${index}.itemDtos`, []); itemsList.forEach(p => {
} newNames[p.id] = p.nameRu || p.name || String(p.id);
});
return { ...prev, [packetIndex]: newNames };
} else {
// remove all products of current page
const newNames = { ...prevNames };
itemsList.forEach(p => {
delete newNames[p.id];
});
return { ...prev, [packetIndex]: newNames };
}
});
setValue(
`packetItemDtos.${packetIndex}.itemDtos`,
checked
? Array.from(new Set([...(watch(`packetItemDtos.${packetIndex}.itemDtos`) || []), ...itemsList.map(p => p.id)]))
: (watch(`packetItemDtos.${packetIndex}.itemDtos`) || []).filter(id => !itemsList.some(p => p.id === id))
);
};
const handleProductChange = (packetIndex: number, product: any, checked: boolean) => {
setSelectedProductNames(prev => {
const prevNames = prev[packetIndex] || {};
if (checked) {
return { ...prev, [packetIndex]: { ...prevNames, [product.id]: product.nameRu || product.name || String(product.id) } };
} else {
const newNames = { ...prevNames };
delete newNames[product.id];
return { ...prev, [packetIndex]: newNames };
}
});
}; };
return ( return (
@@ -282,6 +310,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
justifyContent={'space-between'} justifyContent={'space-between'}
alignItems={'center'} alignItems={'center'}
> >
<OutlinedInput onChange={e => setKeyword(e.target.value)} />
<div style={{ width: '100%' }}> <div style={{ width: '100%' }}>
<FormControl fullWidth> <FormControl fullWidth>
<InputLabel id={`packet-select-label-${index}`}>{t('packet')}</InputLabel> <InputLabel id={`packet-select-label-${index}`}>{t('packet')}</InputLabel>
@@ -290,41 +319,45 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
control={control} control={control}
rules={{ required: requiredText }} rules={{ required: requiredText }}
render={({ field }) => ( render={({ field }) => (
<Select <>
{...field} <Select
labelId={`packet-select-label-${index}`} {...field}
label={t('packet')} labelId={`packet-select-label-${index}`}
onChange={e => { label={t('packet')}
field.onChange(e); value={field.value || ''}
handlePacketChange(index, e.target.value as number); disabled={isLoadingPackets}
}} renderValue={selected =>
value={field.value || ''} paketName || packetsList.find(p => p.id === selected)?.name || t('select')
disabled={isLoadingPackets} }
MenuProps={{ MenuProps={{
PaperProps: { PaperProps: {
style: { maxHeight: 280 }, style: { maxHeight: 280 },
ref: packetScrollRef, ref: packetScrollRef,
onScroll: handlePacketScroll, onScroll: handlePacketScroll,
}, },
}} disableAutoFocus: true,
> autoFocus: false,
{isLoadingPackets && packetsList.length === 0 ? ( }}
<MenuItem disabled> onClick={e => e.stopPropagation()}
<CircularProgress size={24} /> >
</MenuItem> {isLoadingPackets && packetsList.length === 0 ? (
) : ( <MenuItem disabled>
packetsList.map(packet => ( <CircularProgress size={24} />
<MenuItem key={packet.id} value={packet.id}>
{packet.name || `Box ${packet.id}`}
</MenuItem> </MenuItem>
)) ) : (
)} packetsList.map(packet => (
{isLoadingPackets && packetsList.length > 0 && ( <MenuItem key={packet.id} value={packet.id} onClick={() => setPaketName(packet.name)}>
<MenuItem disabled> {packet.name}
<CircularProgress size={20} /> </MenuItem>
</MenuItem> ))
)} )}
</Select> {isLoadingPackets && packetsList.length > 0 && (
<MenuItem disabled>
<CircularProgress size={20} />
</MenuItem>
)}
</Select>
</>
)} )}
/> />
{!!get(errors, `packetItemDtos.${index}.packetId`) && ( {!!get(errors, `packetItemDtos.${index}.packetId`) && (
@@ -355,7 +388,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
<CircularProgress /> <CircularProgress />
) : ( ) : (
<FormControl fullWidth> <FormControl fullWidth>
<Box display='flex' alignItems='center' mb={1}> {/* <Box display='flex' alignItems='center' mb={1}>
<Checkbox <Checkbox
indeterminate={selectedProducts.length > 0 && selectedProducts.length < itemsList.length} indeterminate={selectedProducts.length > 0 && selectedProducts.length < itemsList.length}
checked={itemsList.length > 0 && selectedProducts.length === itemsList.length} checked={itemsList.length > 0 && selectedProducts.length === itemsList.length}
@@ -363,7 +396,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
disabled={itemsList.length === 0} disabled={itemsList.length === 0}
/> />
<Typography variant='body2'>{t('select_all')}</Typography> <Typography variant='body2'>{t('select_all')}</Typography>
</Box> </Box> */}
<Controller <Controller
name={`packetItemDtos.${index}.itemDtos`} name={`packetItemDtos.${index}.itemDtos`}
control={control} control={control}
@@ -382,32 +415,56 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
}} }}
renderValue={selected => ( renderValue={selected => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}> <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{(selected as any[]).map(value => ( {(selected as any[]).map(id => (
<Chip <Chip
key={value} key={id}
label={itemsList.find(p => p.id === value)?.name} label={
selectedProductNames[index]?.[id] ||
itemsList.find(p => p.id === id)?.nameRu ||
itemsList.find(p => p.id === id)?.name ||
id
}
onDelete={e => { onDelete={e => {
e.stopPropagation(); e.stopPropagation();
field.onChange(field.value.filter((id: any) => id !== value)); field.onChange(field.value.filter((x: any) => x !== id));
handleProductChange(index, { id }, false);
}} }}
/> />
))} ))}
</Box> </Box>
)} )}
> >
<MenuItem>
<Checkbox
indeterminate={field.value.length > 0 && field.value.length < itemsList.length}
checked={
itemsList.length > 0 && itemsList.every((p: any) => field.value.includes(p.id))
}
onChange={e => handleSelectAll(e.target.checked, index, packetId, itemsList)}
/>
<Typography variant='body2'>{t('select_all')}</Typography>
</MenuItem>
{itemsList.map(product => ( {itemsList.map(product => (
<MenuItem key={product.id} value={product.id} onClick={e => e.stopPropagation()}> <MenuItem
key={product.id}
value={product.id}
onClick={e => {
e.stopPropagation();
const checked = !field.value.includes(product.id);
let newValue;
if (checked) {
newValue = [...field.value, product.id];
} else {
newValue = field.value.filter((x: any) => x !== product.id);
}
field.onChange(newValue);
handleProductChange(index, product, checked);
}}
>
<Checkbox checked={field.value.includes(product.id)} /> <Checkbox checked={field.value.includes(product.id)} />
<ListItemText primary={product.name} /> <ListItemText primary={product.nameRu || product.name || product.id} />
</MenuItem> </MenuItem>
))} ))}
{isLoadingProducts && itemsHasMore && (
<MenuItem disabled>
<Box display='flex' justifyContent='center' width='100%'>
<CircularProgress size={20} />
</Box>
</MenuItem>
)}
</Select> </Select>
)} )}
/> />