Merge branch 'samandar' into 'dev'
add boxes See merge request azizziy/cpost!30
This commit is contained in:
@@ -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 });
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user