This commit is contained in:
Samandar Turg'unboev
2025-06-26 17:01:16 +05:00
parent 73efc90e11
commit ace1516293
9 changed files with 332 additions and 100 deletions

View File

@@ -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>
);

View File

@@ -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 && (
<>

View File

@@ -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>