@@ -3,7 +3,7 @@
|
|||||||
import Loader from '@/components/common/Loader';
|
import Loader from '@/components/common/Loader';
|
||||||
import { Scrollbar } from '@/components/common/Scrollbar';
|
import { Scrollbar } from '@/components/common/Scrollbar';
|
||||||
import { box_requests } from '@/data/box/box.requests';
|
import { box_requests } from '@/data/box/box.requests';
|
||||||
import { Box, styled, SxProps, Table, TableBody, TableCell, TableHead, TableRow, Theme } from '@mui/material';
|
import { Box, styled, type SxProps, Table, TableBody, TableCell, TableHead, TableRow, type Theme } from '@mui/material';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export interface ColumnData<Data, DataKey = keyof Data> {
|
export interface ColumnData<Data, DataKey = keyof Data> {
|
||||||
@@ -72,10 +72,34 @@ const MyTable = <Data extends { id: number | string }>(props: Props<Data>) => {
|
|||||||
|
|
||||||
const [boxStatuses, setBoxStatuses] = React.useState<Record<string, boolean>>({});
|
const [boxStatuses, setBoxStatuses] = React.useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
|
// Enhanced sorting: completed boxes (green) go to the bottom
|
||||||
|
const sortedData = React.useMemo(() => {
|
||||||
|
return [...data].sort((a: any, b: any) => {
|
||||||
|
const aPrint = a.print ? 1 : 0;
|
||||||
|
const bPrint = b.print ? 1 : 0;
|
||||||
|
|
||||||
|
if (aPrint !== bPrint) {
|
||||||
|
return aPrint - bPrint; // false (0) avval, true (1) keyin
|
||||||
|
}
|
||||||
|
|
||||||
|
const aStatus = boxStatuses[a.id] ? 1 : 0;
|
||||||
|
const bStatus = boxStatuses[b.id] ? 1 : 0;
|
||||||
|
|
||||||
|
if (aStatus !== bStatus) {
|
||||||
|
return aStatus - bStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(a.id) - Number(b.id); // fallback sort
|
||||||
|
});
|
||||||
|
}, [data, boxStatuses]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const fetchBoxStatuses = async () => {
|
const fetchBoxStatuses = async () => {
|
||||||
|
if (!data.length || loading) return;
|
||||||
|
|
||||||
const statuses: Record<string, boolean> = {};
|
const statuses: Record<string, boolean> = {};
|
||||||
|
|
||||||
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
data.map(async row => {
|
data.map(async row => {
|
||||||
try {
|
try {
|
||||||
@@ -91,20 +115,22 @@ const MyTable = <Data extends { id: number | string }>(props: Props<Data>) => {
|
|||||||
{ totalAmount: 0, totalAccepted: 0 }
|
{ totalAmount: 0, totalAccepted: 0 }
|
||||||
);
|
);
|
||||||
|
|
||||||
statuses[row.id] = total.totalAmount === total.totalAccepted;
|
// Box is complete (green) when all items are accepted
|
||||||
|
statuses[row.id] = total.totalAmount === total.totalAccepted && total.totalAmount > 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching box status:', error);
|
console.error(`Error fetching box status for ${row.id}:`, error);
|
||||||
statuses[row.id] = false;
|
statuses[row.id] = false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
setBoxStatuses(statuses);
|
setBoxStatuses(statuses);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching box statuses:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!loading && data.length > 0) {
|
|
||||||
fetchBoxStatuses();
|
fetchBoxStatuses();
|
||||||
}
|
|
||||||
}, [data, loading]);
|
}, [data, loading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -141,19 +167,37 @@ const MyTable = <Data extends { id: number | string }>(props: Props<Data>) => {
|
|||||||
</StyledTableCell>
|
</StyledTableCell>
|
||||||
</StyledTableRow>
|
</StyledTableRow>
|
||||||
) : (
|
) : (
|
||||||
data.map((row: any, rowIndex) => {
|
sortedData.map((row: any, rowIndex) => {
|
||||||
const status = boxStatuses[row.id];
|
const isCompleted = boxStatuses[row.id];
|
||||||
|
console.log(row, 'rows');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTableRow
|
<StyledTableRow
|
||||||
key={row.id}
|
key={row.id}
|
||||||
sx={{
|
sx={{
|
||||||
background: !row.hasInvoice ? 'inherit' : status ? '#a3e635' : '#f87171',
|
background: row.print
|
||||||
...(onClickRow
|
? '#dbeafe' // ko‘k rang (bg-blue-100)
|
||||||
|
: !row.hasInvoice
|
||||||
|
? 'inherit'
|
||||||
|
: isCompleted
|
||||||
|
? '#dcfce7' // yashil (bg-green-100)
|
||||||
|
: '#fef2f2', // qizil (bg-red-100)
|
||||||
|
borderLeft: !row.hasInvoice
|
||||||
|
? 'inherit'
|
||||||
|
: row.print
|
||||||
|
? '4px solid #3b82f6' // ko‘k chegara (border-blue-500)
|
||||||
|
: isCompleted
|
||||||
|
? '4px solid #22c55e' // yashil chegara
|
||||||
|
: '4px solid #ef4444', // qizil chegara
|
||||||
|
...(onClickRow && !row.hasInvoice
|
||||||
? {
|
? {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: '#f1f1f1',
|
backgroundColor: row.print
|
||||||
|
? '#bfdbfe' // hover ko‘k (bg-blue-200)
|
||||||
|
: isCompleted
|
||||||
|
? '#bbf7d0' // hover yashil (bg-green-200)
|
||||||
|
: '#fee2e2', // hover qizil (bg-red-200)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import * as React from 'react';
|
import BaseButton from '@/components/ui-kit/BaseButton';
|
||||||
import { styled, alpha } from '@mui/material/styles';
|
import { BoxStatus } from '@/data/box/box.model';
|
||||||
|
import { PartyStatus, PrintStatus } from '@/data/party/party.model';
|
||||||
|
import { getBoxStatusStyles } from '@/theme/getStatusBoxStyles';
|
||||||
|
import { ArrowDropDown } from '@mui/icons-material';
|
||||||
|
import { PopoverOrigin, SvgIcon, Typography } from '@mui/material';
|
||||||
import Menu, { MenuProps } from '@mui/material/Menu';
|
import Menu, { MenuProps } from '@mui/material/Menu';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import { Box, CircularProgress, IconButton, PopoverOrigin, SvgIcon, Typography } from '@mui/material';
|
import { alpha, styled } from '@mui/material/styles';
|
||||||
import { ArrowDropDown, MoreVert } from '@mui/icons-material';
|
import * as React from 'react';
|
||||||
import { getBoxStatusStyles } from '@/theme/getStatusBoxStyles';
|
|
||||||
import { BoxStatus, IBox } from '@/data/box/box.model';
|
|
||||||
import { PartyStatus } from '@/data/party/party.model';
|
|
||||||
import BaseButton from '@/components/ui-kit/BaseButton';
|
|
||||||
|
|
||||||
type PlacementType = {
|
type PlacementType = {
|
||||||
anchorOrigin?: PopoverOrigin;
|
anchorOrigin?: PopoverOrigin;
|
||||||
@@ -53,7 +53,7 @@ type StatusChangePopupProps = {
|
|||||||
placement?: PlacementType;
|
placement?: PlacementType;
|
||||||
anchor: {
|
anchor: {
|
||||||
text: React.ReactNode;
|
text: React.ReactNode;
|
||||||
status: BoxStatus | PartyStatus;
|
status: BoxStatus | PartyStatus | PrintStatus;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { ForwardedRef, forwardRef } from 'react';
|
import { ForwardedRef, forwardRef } from 'react';
|
||||||
import ReactSelect, { StylesConfig } from 'react-select';
|
import ReactSelect, { StylesConfig } from 'react-select';
|
||||||
|
|
||||||
export const selectDefaultStyles: StylesConfig = {
|
export const selectDefaultStyles: StylesConfig = {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export type BoxStatus = 'READY_TO_INVOICE' | 'READY';
|
export type BoxStatus = 'READY_TO_INVOICE' | 'READY';
|
||||||
|
export type PrintStatus = 'false' | 'true';
|
||||||
export const BoxStatusList: BoxStatus[] = ['READY_TO_INVOICE', 'READY'];
|
export const BoxStatusList: BoxStatus[] = ['READY_TO_INVOICE', 'READY'];
|
||||||
|
|
||||||
export interface IBox {
|
export interface IBox {
|
||||||
@@ -16,6 +17,7 @@ export interface IBox {
|
|||||||
hasInvoice: boolean;
|
hasInvoice: boolean;
|
||||||
totalItems: number;
|
totalItems: number;
|
||||||
status: BoxStatus;
|
status: BoxStatus;
|
||||||
|
print: PrintStatus;
|
||||||
totalBrutto: number;
|
totalBrutto: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,18 +79,19 @@ export type CreateBoxBodyType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateBoxBodyType = {
|
export type UpdateBoxBodyType = {
|
||||||
passportId: number;
|
passportId?: number;
|
||||||
status: BoxStatus;
|
status?: BoxStatus;
|
||||||
packetId: string;
|
packetId?: string;
|
||||||
|
print?: boolean;
|
||||||
// clientId: number;
|
// clientId: number;
|
||||||
cargoId: string;
|
cargoId?: string;
|
||||||
|
|
||||||
// type: string;
|
// type: string;
|
||||||
// name: string;
|
// name: string;
|
||||||
// volume: string;
|
// volume: string;
|
||||||
// boxWeight: number;
|
// boxWeight: number;
|
||||||
// brutto: number;
|
// brutto: number;
|
||||||
items: {
|
items?: {
|
||||||
cargoId: string;
|
cargoId: string;
|
||||||
trekId: string;
|
trekId: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ export type Party = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type PartyStatus = 'COLLECTING' | 'ON_THE_WAY' | 'IN_CUSTOMS' | 'IN_WAREHOUSE' | 'ARRIVED' | 'DELIVERED';
|
export type PartyStatus = 'COLLECTING' | 'ON_THE_WAY' | 'IN_CUSTOMS' | 'IN_WAREHOUSE' | 'ARRIVED' | 'DELIVERED';
|
||||||
|
export type PrintStatus = 'false' | 'true';
|
||||||
export const PartyStatusList: PartyStatus[] = ['COLLECTING', 'ON_THE_WAY', 'IN_CUSTOMS', 'IN_WAREHOUSE', 'ARRIVED', 'DELIVERED'];
|
export const PartyStatusList: PartyStatus[] = ['COLLECTING', 'ON_THE_WAY', 'IN_CUSTOMS', 'IN_WAREHOUSE', 'ARRIVED', 'DELIVERED'];
|
||||||
export const PartyStatusOptions: {
|
export const PartyStatusOptions: {
|
||||||
label: string;
|
label: string;
|
||||||
value: PartyStatus;
|
value: PartyStatus | PrintStatus;
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
label: 'COLLECTING',
|
label: 'COLLECTING',
|
||||||
@@ -19,6 +20,18 @@ export const PartyStatusOptions: {
|
|||||||
label: 'ON_THE_WAY',
|
label: 'ON_THE_WAY',
|
||||||
value: 'ON_THE_WAY',
|
value: 'ON_THE_WAY',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Chop etilmagan',
|
||||||
|
value: 'false',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Chop etilgan',
|
||||||
|
value: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ON_THE_WAY',
|
||||||
|
value: 'ON_THE_WAY',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'IN_CUSTOMS',
|
label: 'IN_CUSTOMS',
|
||||||
value: 'IN_CUSTOMS',
|
value: 'IN_CUSTOMS',
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => {
|
|||||||
|
|
||||||
const item_delete_promises = initialValues.products_list
|
const item_delete_promises = initialValues.products_list
|
||||||
.filter(item => {
|
.filter(item => {
|
||||||
if (!updateBody.items.find(i => String(i.id) === String(item.id))) {
|
if (!updateBody.items?.find(i => String(i.id) === String(item.id))) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const BoxesPrint = forwardRef<HTMLDivElement, BoxesPrintProps>(({ boxData, key }
|
|||||||
}}
|
}}
|
||||||
key={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={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -65,7 +65,7 @@ const BoxesPrint = forwardRef<HTMLDivElement, BoxesPrintProps>(({ boxData, key }
|
|||||||
gap: '12px',
|
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
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
@@ -75,18 +75,21 @@ const BoxesPrint = forwardRef<HTMLDivElement, BoxesPrintProps>(({ boxData, key }
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography sx={{ color: 'black', fontSize: '10px' }}>CPOST EXPRESS CARGO</Typography>
|
<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' }}>
|
||||||
<Typography sx={{ color: 'black', fontSize: '10px' }}>{boxData?.client_id}</Typography>
|
Reys: {boxData?.partyName}-{boxData?.client_id}
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ color: 'black', fontSize: '10px' }}>TEL: +(998) 90 113 44 77</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', gap: '4px', marginTop: '5px', marginRight: '5px' }}>
|
<Box sx={{ display: 'flex', gap: '10px', marginTop: '5px', marginRight: '5px' }}>
|
||||||
<Image alt='telegram' src={TelegramChanel} width={15} height={15} priority unoptimized />
|
<Image alt='telegram' src={TelegramChanel} width={50} height={50} priority unoptimized />
|
||||||
<Image alt='instagram' src={InstagramChanel} width={15} height={15} priority unoptimized />
|
<Image alt='instagram' src={InstagramChanel} width={50} height={50} priority unoptimized />
|
||||||
<Typography sx={{ color: 'black', fontSize: '8px' }}>TEL: +(998) 90 113 44 77</Typography>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
border: '1px solid black',
|
||||||
borderTop: '1px solid black',
|
borderTop: '1px solid black',
|
||||||
textAlign: 'start',
|
textAlign: 'start',
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
@@ -100,7 +103,7 @@ const BoxesPrint = forwardRef<HTMLDivElement, BoxesPrintProps>(({ boxData, key }
|
|||||||
{boxData?.products_list.map((list, index) => (
|
{boxData?.products_list.map((list, index) => (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
borderRight: '1px solid black',
|
border: '1px solid black',
|
||||||
textAlign: 'start',
|
textAlign: 'start',
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
@@ -112,7 +115,6 @@ const BoxesPrint = forwardRef<HTMLDivElement, BoxesPrintProps>(({ boxData, key }
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import BaseInput from '@/components/ui-kit/BaseInput';
|
|||||||
import BasePagination from '@/components/ui-kit/BasePagination';
|
import BasePagination from '@/components/ui-kit/BasePagination';
|
||||||
import { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect';
|
import { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect';
|
||||||
import { useAuthContext } from '@/context/auth-context';
|
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 { box_requests } from '@/data/box/box.requests';
|
||||||
import type { Product, UpdateProductBodyType } from '@/data/item/item.mode';
|
import type { Product, UpdateProductBodyType } from '@/data/item/item.mode';
|
||||||
import { item_requests } from '@/data/item/item.requests';
|
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 { file_service } from '@/services/file-service';
|
||||||
import { notifyUnknownError } from '@/services/notification';
|
import { notifyUnknownError } from '@/services/notification';
|
||||||
import { getStatusColor } from '@/theme/getStatusBoxStyles';
|
import { getStatusColor } from '@/theme/getStatusBoxStyles';
|
||||||
import { Add, Circle, Delete, Download, Edit, FilterList, FilterListOff, Print, RemoveRedEye, Search } from '@mui/icons-material';
|
import {
|
||||||
import { Box, Button, Card, CardContent, Modal, Stack, TextField, Typography } from '@mui/material';
|
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 { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import AsyncSelect from 'react-select/async';
|
import AsyncSelect from 'react-select/async';
|
||||||
@@ -61,11 +73,13 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
const [boxStatusFilter, setBoxStatusFilter] = useState<BoxStatus | undefined>(undefined);
|
const [boxStatusFilter, setBoxStatusFilter] = useState<BoxStatus | undefined>(undefined);
|
||||||
const [trackId, setTrackId] = useState<string>();
|
const [trackId, setTrackId] = useState<string>();
|
||||||
const [partyFilter, setPartyFilter] = useState<{ label: string; value: number } | undefined>(undefined);
|
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 [deleteIds, setDeleteIds] = useState<number[]>([]);
|
||||||
const [downloadIds, setDownloadIds] = useState<number[]>([]);
|
const [downloadIds, setDownloadIds] = useState<number[]>([]);
|
||||||
const [changeStatusIds, setChangeStatusIds] = useState<number[]>([]);
|
const [changeStatusIds, setChangeStatusIds] = useState<number[]>([]);
|
||||||
const [boxAmounts, setBoxAmounts] = useState<Record<number, { totalAmount: number; totalAccepted: number }>>({});
|
const [boxAmounts, setBoxAmounts] = useState<Record<number, { totalAmount: number; totalAccepted: number }>>({});
|
||||||
|
const [printStatuses, setPrintStatuses] = useState<Record<number, string>>({});
|
||||||
|
|
||||||
// Print uchun state
|
// Print uchun state
|
||||||
const [selectedBoxForPrint, setSelectedBoxForPrint] = useState<IBox | null>(null);
|
const [selectedBoxForPrint, setSelectedBoxForPrint] = useState<IBox | null>(null);
|
||||||
@@ -73,8 +87,6 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
|
|
||||||
const printRef = useRef<HTMLDivElement>(null);
|
const printRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Available status options (simulating admin/user permissions)
|
|
||||||
|
|
||||||
// Print functionality
|
// Print functionality
|
||||||
const handlePrint = useReactToPrint({
|
const handlePrint = useReactToPrint({
|
||||||
contentRef: printRef,
|
contentRef: printRef,
|
||||||
@@ -100,7 +112,6 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
|
|
||||||
const onPrintBox = async (boxData: IBox) => {
|
const onPrintBox = async (boxData: IBox) => {
|
||||||
try {
|
try {
|
||||||
// Fetch detailed box data
|
|
||||||
const response = await box_requests.find({ packetId: boxData.id });
|
const response = await box_requests.find({ packetId: boxData.id });
|
||||||
const boxOne = response.data.data;
|
const boxOne = response.data.data;
|
||||||
|
|
||||||
@@ -158,6 +169,14 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
return p;
|
return p;
|
||||||
}, [isAdmin]);
|
}, [isAdmin]);
|
||||||
|
|
||||||
|
const printOptions = useMemo(() => {
|
||||||
|
const p = ['false'] as PrintStatus[];
|
||||||
|
if (isAdmin) {
|
||||||
|
p.push('false');
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}, [isAdmin]);
|
||||||
|
|
||||||
const getBoxesQuery = useRequest(
|
const getBoxesQuery = useRequest(
|
||||||
() =>
|
() =>
|
||||||
box_requests.getAll({
|
box_requests.getAll({
|
||||||
@@ -181,9 +200,11 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
item_requests.getAll({
|
item_requests.getAll({
|
||||||
page: page,
|
page: page,
|
||||||
trekId: trackId,
|
trekId: trackId,
|
||||||
|
packetId: boxFilter?.value,
|
||||||
|
partyId: partyFilter?.value,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
dependencies: [page, trackId],
|
dependencies: [page, trackId, boxFilter?.value, partyFilter?.value],
|
||||||
selectData(data) {
|
selectData(data) {
|
||||||
return data.data.data;
|
return data.data.data;
|
||||||
},
|
},
|
||||||
@@ -232,8 +253,24 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
setPage(1);
|
setPage(1);
|
||||||
setKeyword('');
|
setKeyword('');
|
||||||
setBoxStatusFilter(undefined);
|
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) => {
|
const onDelete = async (id: number) => {
|
||||||
if (deleteIds.includes(id)) return;
|
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(() => {
|
useEffect(() => {
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
setPage(1);
|
setPage(1);
|
||||||
getBoxesQuery.refetch();
|
getBoxesQuery.refetch();
|
||||||
}, 350);
|
}, 350);
|
||||||
return () => clearTimeout(timeoutId);
|
return () => clearTimeout(timeoutId);
|
||||||
}, [keyword, partyFilter?.value]);
|
}, [keyword, partyFilter?.value, boxFilter?.value]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAmounts = async () => {
|
const fetchAmounts = async () => {
|
||||||
@@ -319,6 +369,14 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
}
|
}
|
||||||
}, [list, loading]);
|
}, [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>[] = [
|
const columns: ColumnData<IBox>[] = [
|
||||||
{
|
{
|
||||||
label: t('No'),
|
label: t('No'),
|
||||||
@@ -361,10 +419,16 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
renderCell: data => {
|
renderCell: data => {
|
||||||
const total = boxAmounts[data.id];
|
const total = boxAmounts[data.id];
|
||||||
if (!total) return <Typography>...</Typography>;
|
if (!total) return <Typography>...</Typography>;
|
||||||
|
|
||||||
|
const isCompleted = total.totalAmount === total.totalAccepted && total.totalAmount > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Stack direction='row' alignItems='center' spacing={1}>
|
||||||
<Typography>
|
<Typography>
|
||||||
{total.totalAmount} | {total.totalAccepted}
|
{total.totalAmount} | {total.totalAccepted}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{isCompleted && <CheckCircle sx={{ color: '#22c55e', fontSize: 16 }} />}
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -459,13 +523,72 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
},
|
},
|
||||||
renderCell(data) {
|
renderCell(data) {
|
||||||
const total = boxAmounts[data.id];
|
const total = boxAmounts[data.id];
|
||||||
|
const isCompleted = total?.totalAccepted === total?.totalAmount && total?.totalAmount > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={() => onPrintBox(data)} disabled={total?.totalAccepted !== total?.totalAmount}>
|
<Button onClick={() => onPrintBox(data)}>
|
||||||
<Print className='h-3 w-3 mr-1' />
|
<Print className='h-3 w-3 mr-1' />
|
||||||
</Button>
|
</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: '',
|
label: '',
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -572,6 +695,7 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
{t('create_packet')}
|
{t('create_packet')}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<Button onClick={handleOpen}>{t('product_inspection')}</Button>
|
<Button onClick={handleOpen}>{t('product_inspection')}</Button>
|
||||||
|
|
||||||
<Modal open={open} onClose={handleClose} aria-labelledby='modal-modal-title' aria-describedby='modal-modal-description'>
|
<Modal open={open} onClose={handleClose} aria-labelledby='modal-modal-title' aria-describedby='modal-modal-description'>
|
||||||
<Box sx={style}>
|
<Box sx={style}>
|
||||||
<Typography id='modal-modal-title' variant='h6' component='h2'>
|
<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 }}>
|
<Typography id='modal-modal-description' sx={{ mt: 2 }}>
|
||||||
{t('enter_product')}
|
{t('enter_product')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<AsyncSelect
|
||||||
id='outlined-basic'
|
isClearable
|
||||||
label={t('track_id')}
|
value={partyFilter}
|
||||||
variant='outlined'
|
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}
|
value={trackId}
|
||||||
onChange={e => setTrackId(e.target.value)}
|
onChange={e => setTrackId(e.target.value)}
|
||||||
|
placeholder={t('filter_item_name')}
|
||||||
/>
|
/>
|
||||||
{trackId && trackId.length > 0 && (
|
{trackId && trackId.length > 0 && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
|||||||
const [allPackets, setAllPackets] = useState<any[]>([]);
|
const [allPackets, setAllPackets] = useState<any[]>([]);
|
||||||
// Barcha mahsulotlarni barcha sahifadan yuklash uchun map
|
// Barcha mahsulotlarni barcha sahifadan yuklash uchun map
|
||||||
const [allItemsMap, setAllItemsMap] = useState<{ [packetId: number]: any[] }>({});
|
const [allItemsMap, setAllItemsMap] = useState<{ [packetId: number]: any[] }>({});
|
||||||
|
console.log(initialValues?.partyName);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@@ -529,8 +530,17 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
|||||||
field.onChange(e);
|
field.onChange(e);
|
||||||
handlePartyChange(e);
|
handlePartyChange(e);
|
||||||
}}
|
}}
|
||||||
value={field.value || ''}
|
value={field.value || initialValues?.partyId || ''} // Bu yerda partyId ishlatish kerak
|
||||||
disabled={isLoadingParties}
|
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 ? (
|
{isLoadingParties ? (
|
||||||
<MenuItem disabled>
|
<MenuItem disabled>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { BoxStatus } from '@/data/box/box.model';
|
import { BoxStatus } from '@/data/box/box.model';
|
||||||
import { PartyStatus } from '@/data/party/party.model';
|
import { PartyStatus, PrintStatus } from '@/data/party/party.model';
|
||||||
|
|
||||||
export function getStatusColor(status: BoxStatus | PartyStatus) {
|
export function getStatusColor(status: BoxStatus | PartyStatus | PrintStatus) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'COLLECTING': {
|
case 'COLLECTING': {
|
||||||
return '#FD9C2B';
|
return '#FD9C2B';
|
||||||
@@ -24,13 +24,19 @@ export function getStatusColor(status: BoxStatus | PartyStatus) {
|
|||||||
case 'IN_CUSTOMS': {
|
case 'IN_CUSTOMS': {
|
||||||
return '#C9A26E';
|
return '#C9A26E';
|
||||||
}
|
}
|
||||||
|
case 'false': {
|
||||||
|
return '#C9A26E';
|
||||||
|
}
|
||||||
|
case 'true': {
|
||||||
|
return '#3489E4';
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return '#17D792';
|
return '#17D792';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBoxStatusStyles(status: BoxStatus | PartyStatus) {
|
export function getBoxStatusStyles(status: BoxStatus | PartyStatus | PrintStatus) {
|
||||||
let color = getStatusColor(status);
|
let color = getStatusColor(status);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user