@@ -1,4 +1,4 @@
|
||||
import { IBox, CreateBoxBodyType, UpdateBoxBodyType, IBoxDetail, BoxStatus } from '@/data/box/box.model';
|
||||
import { BoxStatus, CreateBoxBodyType, IBox, IBoxDetail, UpdateBoxBodyType } from '@/data/box/box.model';
|
||||
import { CommonResponseType, PageAble } from '@/helpers/types';
|
||||
import { request } from '@/services/request';
|
||||
import axios from 'axios';
|
||||
@@ -7,6 +7,7 @@ export const box_requests = {
|
||||
async getAll(params?: {
|
||||
page?: number;
|
||||
sort?: string;
|
||||
pageable?: string | number;
|
||||
direction?: string;
|
||||
cargoId?: string;
|
||||
partyId?: string | number;
|
||||
|
||||
@@ -29,7 +29,7 @@ export const item_requests = {
|
||||
async find(params: { itemId?: number | string }) {
|
||||
return request.get<CommonResponseType<Product[]>>('/items/find', { params });
|
||||
},
|
||||
async list(params: { packetId?: number | string }) {
|
||||
async list(params: { packetId?: number | string; page?: number | string }) {
|
||||
return request.get<CommonResponseType<{ data: Product[] }>>('/items/list', { params });
|
||||
},
|
||||
async delete(params: { itemId: number | string }) {
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import get from 'lodash.get';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Controller, useFieldArray, useForm } from 'react-hook-form';
|
||||
|
||||
const StyledCreateBox = styled(Box)`
|
||||
@@ -68,9 +68,35 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
const { push } = useMyNavigation();
|
||||
const [partyId, setPartyId] = useState<number | string>('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
console.log(initialValues);
|
||||
|
||||
// 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 {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
@@ -122,7 +148,6 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
|
||||
const requiredText = t('required');
|
||||
|
||||
// Fetch parties data
|
||||
const { data: parties = [], isLoading: isLoadingParties } = useQuery({
|
||||
queryKey: ['parties-list', 'COLLECTING'],
|
||||
queryFn: () => party_requests.getAll({ status: 'COLLECTING' }),
|
||||
@@ -133,18 +158,6 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
})),
|
||||
});
|
||||
|
||||
// Fetch packets data
|
||||
const { data: packets = [], isLoading: isLoadingPackets } = useQuery({
|
||||
queryKey: ['packets-list', partyId, keyword],
|
||||
queryFn: () =>
|
||||
box_requests.getAll({
|
||||
partyId: partyId,
|
||||
cargoId: keyword,
|
||||
}),
|
||||
select: data => data?.data?.data?.data.filter((box: any) => box.status === 'READY_TO_INVOICE') ?? [],
|
||||
enabled: !!partyId,
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit(async values => {
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -184,6 +197,9 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
setValue('partyName', selectedParty.name);
|
||||
setPartyId(selectedParty.id);
|
||||
setKeyword('');
|
||||
setPacketsPage(1);
|
||||
setPacketsList([]);
|
||||
setPacketsHasMore(true);
|
||||
setValue('packetItemDtos', [{ packetId: 0, itemDtos: [] }]);
|
||||
}
|
||||
};
|
||||
@@ -201,18 +217,55 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
remove(index);
|
||||
};
|
||||
|
||||
// Component for each packet row to isolate product selection
|
||||
const PacketRow = ({ index, field }: { index: number; field: any }) => {
|
||||
const packetId = watch(`packetItemDtos.${index}.packetId`);
|
||||
|
||||
// Fetch products specific to this packet
|
||||
const { data: products, isLoading: isLoadingProducts } = useQuery({
|
||||
queryKey: ['product-list', packetId],
|
||||
queryFn: () => item_requests.list({ packetId }),
|
||||
select: data => data?.data?.data.data ?? [],
|
||||
// Itemlar uchun scroll state
|
||||
const [itemsPage, setItemsPage] = useState(1);
|
||||
const [itemsList, setItemsList] = useState<any[]>([]);
|
||||
const [itemsHasMore, setItemsHasMore] = useState(true);
|
||||
const itemScrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { isLoading: isLoadingProducts } = useQuery({
|
||||
queryKey: ['product-list', packetId, itemsPage],
|
||||
queryFn: () => item_requests.list({ packetId, page: itemsPage }),
|
||||
enabled: !!packetId,
|
||||
onSuccess: data => {
|
||||
const newItems = data?.data?.data?.data || [];
|
||||
setItemsList(prev => (itemsPage === 1 ? newItems : [...prev, ...newItems]));
|
||||
// const totalPages = data?.data?.data || 0;
|
||||
// setItemsHasMore(itemsPage < totalPages);
|
||||
},
|
||||
});
|
||||
|
||||
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(() => {
|
||||
// Reset items list when packet changes
|
||||
setItemsPage(1);
|
||||
setItemsList([]);
|
||||
setItemsHasMore(true);
|
||||
}, [packetId]);
|
||||
|
||||
const selectedProducts = watch(`packetItemDtos.${index}.itemDtos`) || [];
|
||||
const handleSelectAll = (checked: boolean) => {
|
||||
if (checked) {
|
||||
setValue(
|
||||
`packetItemDtos.${index}.itemDtos`,
|
||||
itemsList.map((p: any) => p.id)
|
||||
);
|
||||
} else {
|
||||
setValue(`packetItemDtos.${index}.itemDtos`, []);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className='item-row' mb={2} display={'flex'} flexDirection={'column'}>
|
||||
<div style={{ width: '100%' }}>
|
||||
@@ -247,18 +300,30 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
}}
|
||||
value={field.value || ''}
|
||||
disabled={isLoadingPackets}
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
style: { maxHeight: 280 },
|
||||
ref: packetScrollRef,
|
||||
onScroll: handlePacketScroll,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isLoadingPackets ? (
|
||||
{isLoadingPackets && packetsList.length === 0 ? (
|
||||
<MenuItem disabled>
|
||||
<CircularProgress size={24} />
|
||||
</MenuItem>
|
||||
) : (
|
||||
packets.map(packet => (
|
||||
packetsList.map(packet => (
|
||||
<MenuItem key={packet.id} value={packet.id}>
|
||||
{packet.name || packet.name || `Box ${packet.id}`}
|
||||
{packet.name || `Box ${packet.id}`}
|
||||
</MenuItem>
|
||||
))
|
||||
)}
|
||||
{isLoadingPackets && packetsList.length > 0 && (
|
||||
<MenuItem disabled>
|
||||
<CircularProgress size={20} />
|
||||
</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
@@ -286,30 +351,16 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
<Typography fontSize='18px' sx={{ textAlign: 'start' }} fontWeight={500} color='#5D5850' mb={2}>
|
||||
{t('products')}
|
||||
</Typography>
|
||||
{isLoadingProducts ? (
|
||||
{isLoadingProducts && itemsList.length === 0 ? (
|
||||
<CircularProgress />
|
||||
) : (
|
||||
<FormControl fullWidth>
|
||||
<Box display='flex' alignItems='center' mb={1}>
|
||||
<Controller
|
||||
name={`packetItemDtos.${index}.itemDtos`}
|
||||
control={control}
|
||||
render={({ field: { onChange } }) => (
|
||||
<Checkbox
|
||||
checked={watch(`packetItemDtos.${index}.itemDtos`)?.length === products?.length}
|
||||
onChange={e => {
|
||||
if (e.target.checked) {
|
||||
setValue(`packetItemDtos.${index}.itemDtos`, products?.map(p => p.id) || []);
|
||||
} else {
|
||||
setValue(`packetItemDtos.${index}.itemDtos`, []);
|
||||
}
|
||||
}}
|
||||
indeterminate={
|
||||
watch(`packetItemDtos.${index}.itemDtos`)?.length > 0 &&
|
||||
watch(`packetItemDtos.${index}.itemDtos`)?.length < products?.length!
|
||||
}
|
||||
/>
|
||||
)}
|
||||
indeterminate={selectedProducts.length > 0 && selectedProducts.length < itemsList.length}
|
||||
checked={itemsList.length > 0 && selectedProducts.length === itemsList.length}
|
||||
onChange={e => handleSelectAll(e.target.checked)}
|
||||
disabled={itemsList.length === 0}
|
||||
/>
|
||||
<Typography variant='body2'>{t('select_all')}</Typography>
|
||||
</Box>
|
||||
@@ -319,31 +370,44 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...field}
|
||||
labelId={`product-select-label-${index}`}
|
||||
multiple
|
||||
value={field.value || []}
|
||||
onChange={e => field.onChange(e.target.value)}
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
style: { maxHeight: 280 },
|
||||
ref: itemScrollRef,
|
||||
onScroll: handleItemScroll,
|
||||
},
|
||||
disableAutoFocus: true,
|
||||
autoFocus: false,
|
||||
}}
|
||||
renderValue={selected => (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
{(selected as any[]).map(value => (
|
||||
<Chip
|
||||
key={value}
|
||||
label={products?.find(p => p.id === value)?.name}
|
||||
onDelete={() => {
|
||||
const newValue = (field.value || []).filter(id => id !== value);
|
||||
field.onChange(newValue);
|
||||
label={itemsList.find(p => p.id === value)?.name}
|
||||
onDelete={e => {
|
||||
e.stopPropagation();
|
||||
field.onChange(field.value.filter((id: any) => id !== value));
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
{products?.map(product => (
|
||||
<MenuItem key={product.id} value={product.id}>
|
||||
<Checkbox checked={(field.value || []).includes(product.id)} />
|
||||
{itemsList.map(product => (
|
||||
<MenuItem key={product.id} value={product.id} onClick={e => e.stopPropagation()}>
|
||||
<Checkbox checked={field.value.includes(product.id)} />
|
||||
<ListItemText primary={product.name} />
|
||||
</MenuItem>
|
||||
))}
|
||||
{isLoadingProducts && itemsHasMore && (
|
||||
<MenuItem disabled>
|
||||
<Box display='flex' justifyContent='center' width='100%'>
|
||||
<CircularProgress size={20} />
|
||||
</Box>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user