This commit is contained in:
@@ -54,7 +54,7 @@ const BoxesPrint = forwardRef<HTMLDivElement, BoxesPrintProps>(({ boxData, key }
|
||||
}}
|
||||
key={key}
|
||||
>
|
||||
<Box sx={{ border: '1px solid black' }}>
|
||||
<Box sx={{ border: '2px solid black', borderBottom: 'none' }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -65,7 +65,7 @@ const BoxesPrint = forwardRef<HTMLDivElement, BoxesPrintProps>(({ boxData, key }
|
||||
gap: '12px',
|
||||
}}
|
||||
>
|
||||
<Image alt='logo' src={Logo} width={30} height={30} priority unoptimized />
|
||||
<Image alt='logo' src={Logo} width={55} height={55} priority unoptimized />
|
||||
<Box
|
||||
sx={{
|
||||
color: '#fff',
|
||||
@@ -75,42 +75,44 @@ const BoxesPrint = forwardRef<HTMLDivElement, BoxesPrintProps>(({ boxData, key }
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: 'black', fontSize: '10px' }}>CPOST EXPRESS CARGO</Typography>
|
||||
<Typography sx={{ color: 'black', fontSize: '10px' }}>Reys: {boxData?.partyName}</Typography>
|
||||
<Typography sx={{ color: 'black', fontSize: '10px' }}>{boxData?.client_id}</Typography>
|
||||
<Typography sx={{ color: 'black', fontSize: '10px' }}>
|
||||
Reys: {boxData?.partyName}-{boxData?.client_id}
|
||||
</Typography>
|
||||
<Typography sx={{ color: 'black', fontSize: '10px' }}>TEL: +(998) 90 113 44 77</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: '4px', marginTop: '5px', marginRight: '5px' }}>
|
||||
<Image alt='telegram' src={TelegramChanel} width={15} height={15} priority unoptimized />
|
||||
<Image alt='instagram' src={InstagramChanel} width={15} height={15} priority unoptimized />
|
||||
<Typography sx={{ color: 'black', fontSize: '8px' }}>TEL: +(998) 90 113 44 77</Typography>
|
||||
<Box sx={{ display: 'flex', gap: '10px', marginTop: '5px', marginRight: '5px' }}>
|
||||
<Image alt='telegram' src={TelegramChanel} width={50} height={50} priority unoptimized />
|
||||
<Image alt='instagram' src={InstagramChanel} width={50} height={50} priority unoptimized />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
borderTop: '1px solid black',
|
||||
textAlign: 'start',
|
||||
width: 'auto',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||
'@media print': {
|
||||
pageBreakInside: 'avoid',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{boxData?.products_list.map((list, index) => (
|
||||
<Box
|
||||
sx={{
|
||||
borderRight: '1px solid black',
|
||||
textAlign: 'start',
|
||||
width: 'auto',
|
||||
padding: '4px',
|
||||
}}
|
||||
key={index}
|
||||
>
|
||||
<Typography sx={{ fontSize: '12px' }}>{list.trekId}</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
border: '1px solid black',
|
||||
borderTop: '1px solid black',
|
||||
textAlign: 'start',
|
||||
width: 'auto',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||
'@media print': {
|
||||
pageBreakInside: 'avoid',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{boxData?.products_list.map((list, index) => (
|
||||
<Box
|
||||
sx={{
|
||||
border: '1px solid black',
|
||||
textAlign: 'start',
|
||||
width: 'auto',
|
||||
padding: '4px',
|
||||
}}
|
||||
key={index}
|
||||
>
|
||||
<Typography sx={{ fontSize: '12px' }}>{list.trekId}</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ import BaseInput from '@/components/ui-kit/BaseInput';
|
||||
import BasePagination from '@/components/ui-kit/BasePagination';
|
||||
import { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect';
|
||||
import { useAuthContext } from '@/context/auth-context';
|
||||
import { type BoxStatus, BoxStatusList, type IBox } from '@/data/box/box.model';
|
||||
import { type BoxStatus, BoxStatusList, type IBox, PrintStatus } from '@/data/box/box.model';
|
||||
import { box_requests } from '@/data/box/box.requests';
|
||||
import type { Product, UpdateProductBodyType } from '@/data/item/item.mode';
|
||||
import { item_requests } from '@/data/item/item.requests';
|
||||
@@ -23,8 +23,20 @@ import useRequest from '@/hooks/useRequest';
|
||||
import { file_service } from '@/services/file-service';
|
||||
import { notifyUnknownError } from '@/services/notification';
|
||||
import { getStatusColor } from '@/theme/getStatusBoxStyles';
|
||||
import { Add, Circle, Delete, Download, Edit, FilterList, FilterListOff, Print, RemoveRedEye, Search } from '@mui/icons-material';
|
||||
import { Box, Button, Card, CardContent, Modal, Stack, TextField, Typography } from '@mui/material';
|
||||
import {
|
||||
Add,
|
||||
CheckCircle,
|
||||
Circle,
|
||||
Delete,
|
||||
Download,
|
||||
Edit,
|
||||
FilterList,
|
||||
FilterListOff,
|
||||
Print,
|
||||
RemoveRedEye,
|
||||
Search,
|
||||
} from '@mui/icons-material';
|
||||
import { Box, Button, Card, CardContent, FormControl, MenuItem, Modal, Select, Stack, Typography } from '@mui/material';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import AsyncSelect from 'react-select/async';
|
||||
@@ -61,11 +73,13 @@ const DashboardBoxesPage = (props: Props) => {
|
||||
const [boxStatusFilter, setBoxStatusFilter] = useState<BoxStatus | undefined>(undefined);
|
||||
const [trackId, setTrackId] = useState<string>();
|
||||
const [partyFilter, setPartyFilter] = useState<{ label: string; value: number } | undefined>(undefined);
|
||||
const [boxFilter, setBoxFilter] = useState<{ label: string; value: number } | undefined>(undefined);
|
||||
|
||||
const [deleteIds, setDeleteIds] = useState<number[]>([]);
|
||||
const [downloadIds, setDownloadIds] = useState<number[]>([]);
|
||||
const [changeStatusIds, setChangeStatusIds] = useState<number[]>([]);
|
||||
const [boxAmounts, setBoxAmounts] = useState<Record<number, { totalAmount: number; totalAccepted: number }>>({});
|
||||
const [printStatuses, setPrintStatuses] = useState<Record<number, string>>({});
|
||||
|
||||
// Print uchun state
|
||||
const [selectedBoxForPrint, setSelectedBoxForPrint] = useState<IBox | null>(null);
|
||||
@@ -73,8 +87,6 @@ const DashboardBoxesPage = (props: Props) => {
|
||||
|
||||
const printRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Available status options (simulating admin/user permissions)
|
||||
|
||||
// Print functionality
|
||||
const handlePrint = useReactToPrint({
|
||||
contentRef: printRef,
|
||||
@@ -100,7 +112,6 @@ const DashboardBoxesPage = (props: Props) => {
|
||||
|
||||
const onPrintBox = async (boxData: IBox) => {
|
||||
try {
|
||||
// Fetch detailed box data
|
||||
const response = await box_requests.find({ packetId: boxData.id });
|
||||
const boxOne = response.data.data;
|
||||
|
||||
@@ -158,6 +169,14 @@ const DashboardBoxesPage = (props: Props) => {
|
||||
return p;
|
||||
}, [isAdmin]);
|
||||
|
||||
const printOptions = useMemo(() => {
|
||||
const p = ['false'] as PrintStatus[];
|
||||
if (isAdmin) {
|
||||
p.push('false');
|
||||
}
|
||||
return p;
|
||||
}, [isAdmin]);
|
||||
|
||||
const getBoxesQuery = useRequest(
|
||||
() =>
|
||||
box_requests.getAll({
|
||||
@@ -181,9 +200,11 @@ const DashboardBoxesPage = (props: Props) => {
|
||||
item_requests.getAll({
|
||||
page: page,
|
||||
trekId: trackId,
|
||||
packetId: boxFilter?.value,
|
||||
partyId: partyFilter?.value,
|
||||
}),
|
||||
{
|
||||
dependencies: [page, trackId],
|
||||
dependencies: [page, trackId, boxFilter?.value, partyFilter?.value],
|
||||
selectData(data) {
|
||||
return data.data.data;
|
||||
},
|
||||
@@ -232,8 +253,24 @@ const DashboardBoxesPage = (props: Props) => {
|
||||
setPage(1);
|
||||
setKeyword('');
|
||||
setBoxStatusFilter(undefined);
|
||||
setPartyFilter(undefined);
|
||||
};
|
||||
|
||||
const { data: defaultBoxOptions, refetch } = useRequest(
|
||||
() =>
|
||||
box_requests.getAll({
|
||||
partyId: partyFilter?.value,
|
||||
}),
|
||||
{
|
||||
enabled: !!partyFilter,
|
||||
selectData(data) {
|
||||
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
|
||||
},
|
||||
placeholderData: [],
|
||||
dependencies: [partyFilter],
|
||||
}
|
||||
);
|
||||
|
||||
const onDelete = async (id: number) => {
|
||||
if (deleteIds.includes(id)) return;
|
||||
|
||||
@@ -277,13 +314,26 @@ const DashboardBoxesPage = (props: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onChangePrint = async (id: number, newStatus: PrintStatus) => {
|
||||
if (changeStatusIds.includes(id)) return;
|
||||
|
||||
try {
|
||||
setChangeStatusIds(p => [...p, id]);
|
||||
getBoxesQuery.refetch();
|
||||
} catch (error) {
|
||||
notifyUnknownError(error);
|
||||
} finally {
|
||||
setChangeStatusIds(prev => prev.filter(i => i !== id));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
setPage(1);
|
||||
getBoxesQuery.refetch();
|
||||
}, 350);
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [keyword, partyFilter?.value]);
|
||||
}, [keyword, partyFilter?.value, boxFilter?.value]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAmounts = async () => {
|
||||
@@ -319,6 +369,14 @@ const DashboardBoxesPage = (props: Props) => {
|
||||
}
|
||||
}, [list, loading]);
|
||||
|
||||
// Calculate completion statistics
|
||||
|
||||
const boxOptions = (inputValue: string) => {
|
||||
return box_requests.getAll({ cargoId: inputValue }).then(res => {
|
||||
return res.data.data.data.map(p => ({ label: p.name, value: p.id }));
|
||||
});
|
||||
};
|
||||
|
||||
const columns: ColumnData<IBox>[] = [
|
||||
{
|
||||
label: t('No'),
|
||||
@@ -361,10 +419,16 @@ const DashboardBoxesPage = (props: Props) => {
|
||||
renderCell: data => {
|
||||
const total = boxAmounts[data.id];
|
||||
if (!total) return <Typography>...</Typography>;
|
||||
|
||||
const isCompleted = total.totalAmount === total.totalAccepted && total.totalAmount > 0;
|
||||
|
||||
return (
|
||||
<Typography>
|
||||
{total.totalAmount} | {total.totalAccepted}
|
||||
</Typography>
|
||||
<Stack direction='row' alignItems='center' spacing={1}>
|
||||
<Typography>
|
||||
{total.totalAmount} | {total.totalAccepted}
|
||||
</Typography>
|
||||
{isCompleted && <CheckCircle sx={{ color: '#22c55e', fontSize: 16 }} />}
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -459,13 +523,72 @@ const DashboardBoxesPage = (props: Props) => {
|
||||
},
|
||||
renderCell(data) {
|
||||
const total = boxAmounts[data.id];
|
||||
const isCompleted = total?.totalAccepted === total?.totalAmount && total?.totalAmount > 0;
|
||||
|
||||
return (
|
||||
<Button onClick={() => onPrintBox(data)} disabled={total?.totalAccepted !== total?.totalAmount}>
|
||||
<Button onClick={() => onPrintBox(data)}>
|
||||
<Print className='h-3 w-3 mr-1' />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
dataKey: 'status',
|
||||
label: t('status'),
|
||||
width: 240,
|
||||
renderHeaderCell(rowIndex) {
|
||||
return (
|
||||
<Stack direction={'row'} alignItems={'center'}>
|
||||
<span>{t('print_status')}</span>
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
renderCell(data) {
|
||||
const currentValue = printStatuses[data.id] || (data.print ? 'true' : 'false');
|
||||
return (
|
||||
<FormControl sx={{ m: 1, border: 'none', minWidth: 120, background: data.print ? '#3489E4' : '#DF2F99' }} size='small'>
|
||||
<Select
|
||||
labelId={`print-status-${data.id}`}
|
||||
id={`print-status-${data.id}`}
|
||||
sx={{ color: 'white', border: 'none' }}
|
||||
value={currentValue}
|
||||
onChange={async e => {
|
||||
const newValue = e.target.value as PrintStatus;
|
||||
|
||||
// Local state yangilanadi
|
||||
setPrintStatuses(prev => ({
|
||||
...prev,
|
||||
[data.id]: newValue,
|
||||
}));
|
||||
|
||||
// Callback chaqiriladi
|
||||
onChangePrint(data.id, newValue);
|
||||
|
||||
// Serverga request yuboriladi
|
||||
try {
|
||||
const res = await box_requests.find({ packetId: data.id });
|
||||
await box_requests.update({
|
||||
cargoId: String(res.data.data.packet.cargoId),
|
||||
items: res.data.data.items,
|
||||
print: newValue === 'true' ? true : newValue === 'false' && false,
|
||||
packetId: String(res.data.data.packet.id),
|
||||
passportId: res.data.data.client.passportId,
|
||||
status: res.data.data.packet.status,
|
||||
});
|
||||
refetch();
|
||||
getBoxesQuery.refetch();
|
||||
} catch (error) {
|
||||
console.error('Print status update failed:', error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem value='true'>Chop etildi</MenuItem>
|
||||
<MenuItem value='false'>Chop etilmadi</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
width: 100,
|
||||
@@ -572,6 +695,7 @@ const DashboardBoxesPage = (props: Props) => {
|
||||
{t('create_packet')}
|
||||
</BaseButton>
|
||||
<Button onClick={handleOpen}>{t('product_inspection')}</Button>
|
||||
|
||||
<Modal open={open} onClose={handleClose} aria-labelledby='modal-modal-title' aria-describedby='modal-modal-description'>
|
||||
<Box sx={style}>
|
||||
<Typography id='modal-modal-title' variant='h6' component='h2'>
|
||||
@@ -580,12 +704,42 @@ const DashboardBoxesPage = (props: Props) => {
|
||||
<Typography id='modal-modal-description' sx={{ mt: 2 }}>
|
||||
{t('enter_product')}
|
||||
</Typography>
|
||||
<TextField
|
||||
id='outlined-basic'
|
||||
label={t('track_id')}
|
||||
variant='outlined'
|
||||
<AsyncSelect
|
||||
isClearable
|
||||
value={partyFilter}
|
||||
onChange={(newValue: any) => {
|
||||
setPartyFilter(newValue);
|
||||
setPage(1);
|
||||
}}
|
||||
styles={selectDefaultStyles}
|
||||
noOptionsMessage={() => t('not_found')}
|
||||
loadingMessage={() => t('loading')}
|
||||
defaultOptions={defaultPartyOptions!}
|
||||
loadOptions={partyOptions}
|
||||
placeholder={t('filter_party_name')}
|
||||
/>
|
||||
|
||||
<AsyncSelect
|
||||
isClearable
|
||||
value={boxFilter}
|
||||
onChange={(newValue: any) => {
|
||||
setBoxFilter(newValue);
|
||||
setPage(1);
|
||||
}}
|
||||
styles={selectDefaultStyles}
|
||||
noOptionsMessage={() => t('enter_box_name_to_find')}
|
||||
loadingMessage={() => t('loading')}
|
||||
defaultOptions={defaultBoxOptions!}
|
||||
loadOptions={boxOptions}
|
||||
placeholder={t('filter_box_name')}
|
||||
/>
|
||||
<BaseInput
|
||||
InputProps={{
|
||||
startAdornment: <Search color='primary' />,
|
||||
}}
|
||||
value={trackId}
|
||||
onChange={e => setTrackId(e.target.value)}
|
||||
placeholder={t('filter_item_name')}
|
||||
/>
|
||||
{trackId && trackId.length > 0 && (
|
||||
<>
|
||||
|
||||
@@ -86,6 +86,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
const [allPackets, setAllPackets] = useState<any[]>([]);
|
||||
// Barcha mahsulotlarni barcha sahifadan yuklash uchun map
|
||||
const [allItemsMap, setAllItemsMap] = useState<{ [packetId: number]: any[] }>({});
|
||||
console.log(initialValues?.partyName);
|
||||
|
||||
const {
|
||||
control,
|
||||
@@ -529,8 +530,17 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
field.onChange(e);
|
||||
handlePartyChange(e);
|
||||
}}
|
||||
value={field.value || ''}
|
||||
value={field.value || initialValues?.partyId || ''} // Bu yerda partyId ishlatish kerak
|
||||
disabled={isLoadingParties}
|
||||
renderValue={selected => {
|
||||
// Agar edit mode bo'lsa va initialValues mavjud bo'lsa
|
||||
if (editMode && initialValues?.partyName && selected === initialValues?.partyId) {
|
||||
return initialValues.partyName;
|
||||
}
|
||||
// Aks holda parties ro'yxatidan topish
|
||||
const selectedParty = parties.find(p => p.id === selected);
|
||||
return selectedParty ? selectedParty.name : '';
|
||||
}}
|
||||
>
|
||||
{isLoadingParties ? (
|
||||
<MenuItem disabled>
|
||||
|
||||
Reference in New Issue
Block a user