added page acceptance
This commit is contained in:
@@ -3,9 +3,9 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env NEXT_PUBLIC_API_URL=https://cpost.felixits.uz next dev --port=3080",
|
"dev": "cross-env NEXT_PUBLIC_API_URL=https://cpcargo.felixits.uz next dev --port=3080",
|
||||||
"build": "cross-env NEXT_PUBLIC_API_URL=https://api.cpcargo.uz next build",
|
"build": "cross-env NEXT_PUBLIC_API_URL=https://api.cpcargo.uz next build",
|
||||||
"build:dev": "cross-env NEXT_PUBLIC_API_URL=https://cpost.felixits.uz next build",
|
"build:dev": "cross-env NEXT_PUBLIC_API_URL=https://cpcargo.felixits.uz next build",
|
||||||
"build:prod": "cross-env NEXT_PUBLIC_API_URL=https://api.cpcargo.uz next build",
|
"build:prod": "cross-env NEXT_PUBLIC_API_URL=https://api.cpcargo.uz next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
|
|||||||
5
src/app/[locale]/dashboard/acceptance/page.tsx
Normal file
5
src/app/[locale]/dashboard/acceptance/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import DashboardAcceptancePage from '@/routes/private/acceptance/DashboardAcceptancePage';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return <DashboardAcceptancePage />;
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ body {
|
|||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
color: #555351;
|
color: #555351;
|
||||||
font-family: 'Inter', 'Arial', sans-serif !important;
|
font-family: 'Inter', 'Arial', sans-serif !important;
|
||||||
overflow-x: hidden;
|
/* overflow-x: hidden; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-layout {
|
.dashboard-layout {
|
||||||
@@ -43,7 +43,7 @@ body {
|
|||||||
border: 0;
|
border: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
clip: rect(0 0 0 0);
|
clip: rect(0 0 0 0);
|
||||||
overflow: hidden;
|
/* overflow: hidden; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-select {
|
.no-select {
|
||||||
|
|||||||
@@ -169,7 +169,6 @@ const MyTable = <Data extends { id: number | string }>(props: Props<Data>) => {
|
|||||||
) : (
|
) : (
|
||||||
sortedData.map((row: any, rowIndex) => {
|
sortedData.map((row: any, rowIndex) => {
|
||||||
const isCompleted = boxStatuses[row.id];
|
const isCompleted = boxStatuses[row.id];
|
||||||
console.log(row, 'rows');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTableRow
|
<StyledTableRow
|
||||||
|
|||||||
@@ -113,4 +113,19 @@ export const routes = [
|
|||||||
),
|
),
|
||||||
roles: [UserRoleEnum.ADMIN],
|
roles: [UserRoleEnum.ADMIN],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Tashkent',
|
||||||
|
path: pageLinks.dashboard.acceptance.index,
|
||||||
|
icon: (
|
||||||
|
<SvgIcon fontSize='small'>
|
||||||
|
<svg xmlns='http://www.w3.org/2000/svg' id='Layer_1' data-name='Layer 1' viewBox='0 0 24 24' width='512' height='512'>
|
||||||
|
<path
|
||||||
|
fill='currentColor'
|
||||||
|
d='M16,2H8c-1.65,0-3,1.35-3,3v12h14V5c0-1.65-1.35-3-3-3Zm-3,10.27c-.47,.47-1.1,.73-1.77,.73s-1.3-.26-1.77-.73l-1.94-1.94,1.41-1.41,1.94,1.94c.13,.13,.28,.15,.35,.15s.23-.02,.35-.15l3.94-3.94,1.41,1.41-3.94,3.94Zm11,2.73v7H0v-7c0-1.65,1.35-3,3-3v7H21v-7c1.65,0,3,1.35,3,3Z'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</SvgIcon>
|
||||||
|
),
|
||||||
|
roles: [UserRoleEnum.ADMIN, UserRoleEnum.CHINA_WORKER],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ export default function BasePagination({ page, pageSize, totalCount, onChange }:
|
|||||||
onChange={(_, newPage) => onChange(newPage)}
|
onChange={(_, newPage) => onChange(newPage)}
|
||||||
variant='outlined'
|
variant='outlined'
|
||||||
shape='rounded'
|
shape='rounded'
|
||||||
|
siblingCount={11}
|
||||||
|
boundaryCount={1}
|
||||||
color='primary'
|
color='primary'
|
||||||
sx={{
|
sx={{
|
||||||
'.Mui-selected': {
|
'.Mui-selected': {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export interface IBoxDetail {
|
|||||||
volume: string;
|
volume: string;
|
||||||
boxWeight: number;
|
boxWeight: number;
|
||||||
brutto: number;
|
brutto: number;
|
||||||
|
print: PrintStatus;
|
||||||
hasInvoice: boolean;
|
hasInvoice: boolean;
|
||||||
status: BoxStatus;
|
status: BoxStatus;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ export const pageLinks = {
|
|||||||
create: '/dashboard/boxes/create',
|
create: '/dashboard/boxes/create',
|
||||||
edit: (slug: string | number) => '/dashboard/boxes/edit/' + slug,
|
edit: (slug: string | number) => '/dashboard/boxes/edit/' + slug,
|
||||||
},
|
},
|
||||||
|
acceptance: {
|
||||||
|
index: '/dashboard/acceptance',
|
||||||
|
// create: '/dashboard/boxes/create',
|
||||||
|
// edit: (slug: string | number) => '/dashboard/boxes/edit/' + slug,
|
||||||
|
},
|
||||||
items: {
|
items: {
|
||||||
index: '/dashboard/items',
|
index: '/dashboard/items',
|
||||||
create: '/dashboard/items/create',
|
create: '/dashboard/items/create',
|
||||||
|
|||||||
13
src/modalStorage/modalSlice.ts
Normal file
13
src/modalStorage/modalSlice.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
interface ModalState {
|
||||||
|
isOpen: boolean;
|
||||||
|
openModal: () => void;
|
||||||
|
closeModal: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useModalStore = create<ModalState>(set => ({
|
||||||
|
isOpen: false,
|
||||||
|
openModal: () => set({ isOpen: true }),
|
||||||
|
closeModal: () => set({ isOpen: false }),
|
||||||
|
}));
|
||||||
15
src/modalStorage/partyId.ts
Normal file
15
src/modalStorage/partyId.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
interface BoxIDState {
|
||||||
|
boxesId: number | undefined;
|
||||||
|
setBoxId: (boxId: number | undefined) => void;
|
||||||
|
boxesIdAccepted: number | undefined;
|
||||||
|
setBoxIdAccepted: (boxId: number | undefined) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useBoxIdStore = create<BoxIDState>(set => ({
|
||||||
|
boxesId: undefined,
|
||||||
|
boxesIdAccepted: undefined,
|
||||||
|
setBoxId: boxId => set({ boxesId: boxId }),
|
||||||
|
setBoxIdAccepted: boxId => set({ boxesIdAccepted: boxId }),
|
||||||
|
}));
|
||||||
746
src/routes/private/acceptance/DashboardAcceptancePage.tsx
Normal file
746
src/routes/private/acceptance/DashboardAcceptancePage.tsx
Normal file
@@ -0,0 +1,746 @@
|
|||||||
|
'use client';
|
||||||
|
import ActionPopMenu from '@/components/common/ActionPopMenu';
|
||||||
|
import { type ColumnData, MyTable } from '@/components/common/MyTable';
|
||||||
|
import BaseButton from '@/components/ui-kit/BaseButton';
|
||||||
|
import BaseInput from '@/components/ui-kit/BaseInput';
|
||||||
|
import { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect';
|
||||||
|
import { useAuthContext } from '@/context/auth-context';
|
||||||
|
import type { BoxStatus, 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';
|
||||||
|
import { party_requests } from '@/data/party/party.requests';
|
||||||
|
import { DEFAULT_PAGE_SIZE, pageLinks } from '@/helpers/constants';
|
||||||
|
import useInput from '@/hooks/useInput';
|
||||||
|
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
||||||
|
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||||
|
import useRequest from '@/hooks/useRequest';
|
||||||
|
import { useModalStore } from '@/modalStorage/modalSlice';
|
||||||
|
import { useBoxIdStore } from '@/modalStorage/partyId';
|
||||||
|
import { notifyUnknownError } from '@/services/notification';
|
||||||
|
import { CheckCircle, FilterListOff, Print, RemoveRedEye, Search } from '@mui/icons-material';
|
||||||
|
import { Box, Button, CircularProgress, FormControl, MenuItem, Select, Stack, Typography } from '@mui/material';
|
||||||
|
import { CloseIcon } from 'next/dist/client/components/react-dev-overlay/internal/icons/CloseIcon';
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
import type React from 'react';
|
||||||
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import AsyncSelect from 'react-select/async';
|
||||||
|
import { useReactToPrint } from 'react-to-print';
|
||||||
|
import BoxesPrintList from '../boxes-print/BoxesPrintList';
|
||||||
|
import CustomModal from '../customModal/CustomModal';
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 400,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '10px',
|
||||||
|
p: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
const DashboardAcceptancePage = () => {
|
||||||
|
// Redux for modal state
|
||||||
|
const { isOpen: isModalOpen, openModal, closeModal } = useModalStore();
|
||||||
|
const t = useMyTranslation();
|
||||||
|
const navigation = useMyNavigation();
|
||||||
|
const { isAdmin } = useAuthContext();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// State management
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||||
|
const { value: keyword, onChange: handleKeyword, setValue: setKeyword } = useInput('');
|
||||||
|
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>>({});
|
||||||
|
const [hasMore, setHasMore] = useState(true);
|
||||||
|
const [isFetching, setIsFetching] = useState(false);
|
||||||
|
const [allData, setAllData] = useState<IBox[]>([]);
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const boxId = searchParams.get('boxId');
|
||||||
|
const { boxesIdAccepted, setBoxIdAccepted } = useBoxIdStore();
|
||||||
|
|
||||||
|
// Print related state
|
||||||
|
const [selectedBoxForPrint, setSelectedBoxForPrint] = useState<IBox | null>(null);
|
||||||
|
const [selectedBoxDetails, setSelectedBoxDetails] = useState<any>(null);
|
||||||
|
const printRef = useRef<HTMLDivElement>(null);
|
||||||
|
const tableContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Print functionality
|
||||||
|
const handlePrint = useReactToPrint({
|
||||||
|
contentRef: printRef,
|
||||||
|
onAfterPrint: () => {
|
||||||
|
setSelectedBoxForPrint(null);
|
||||||
|
setSelectedBoxDetails(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch party options
|
||||||
|
const { data: defaultPartyOptions } = useRequest(() => party_requests.getAll({}), {
|
||||||
|
enabled: true,
|
||||||
|
selectData(data) {
|
||||||
|
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
|
||||||
|
},
|
||||||
|
placeholderData: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const partyOptions = (inputValue: string) => {
|
||||||
|
return party_requests.getAll({ partyName: inputValue }).then(res => {
|
||||||
|
return res.data.data.data.map(p => ({ label: p.name, value: p.id }));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Print box handler
|
||||||
|
const onPrintBox = async (boxData: IBox) => {
|
||||||
|
try {
|
||||||
|
const response = await box_requests.find({ packetId: boxData.id });
|
||||||
|
const boxOne = response.data.data;
|
||||||
|
const detailedBoxData = {
|
||||||
|
id: +boxData.id,
|
||||||
|
box_name: boxOne.packet.name,
|
||||||
|
net_weight: +boxOne.packet.brutto,
|
||||||
|
box_weight: +boxOne.packet.boxWeight,
|
||||||
|
box_type: boxOne.packet.boxType,
|
||||||
|
box_size: boxOne.packet.volume,
|
||||||
|
passportName: boxOne.packet.passportName,
|
||||||
|
status: boxOne.packet.status,
|
||||||
|
packetId: boxData.id,
|
||||||
|
partyId: +boxOne.packet.partyId,
|
||||||
|
partyName: boxOne.packet.partyName,
|
||||||
|
passportId: boxOne.client?.passportId,
|
||||||
|
client_id: boxOne.packet?.cargoId,
|
||||||
|
clientName: boxOne.client?.passportName,
|
||||||
|
products_list: boxOne.items.map((item: any) => ({
|
||||||
|
id: item.id,
|
||||||
|
price: item.price,
|
||||||
|
cargoId: item.cargoId,
|
||||||
|
trekId: item.trekId,
|
||||||
|
name: item.name,
|
||||||
|
nameRu: item.nameRu,
|
||||||
|
amount: +item.amount,
|
||||||
|
acceptedNumber: item.acceptedNumber,
|
||||||
|
weight: +item.weight,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
setSelectedBoxDetails(detailedBoxData);
|
||||||
|
setTimeout(() => {
|
||||||
|
handlePrint();
|
||||||
|
}, 100);
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Memoized options
|
||||||
|
const boxStatusOptions = useMemo(() => {
|
||||||
|
const p = ['READY_TO_INVOICE'] as BoxStatus[];
|
||||||
|
if (isAdmin) {
|
||||||
|
p.push('READY');
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}, [isAdmin]);
|
||||||
|
|
||||||
|
const printOptions = useMemo(() => {
|
||||||
|
const p = ['false'] as PrintStatus[];
|
||||||
|
if (isAdmin) {
|
||||||
|
p.push('false');
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}, [isAdmin]);
|
||||||
|
|
||||||
|
// Main data fetching query with optimized refresh handling
|
||||||
|
const getBoxesQuery = useRequest(
|
||||||
|
() =>
|
||||||
|
box_requests.getAll({
|
||||||
|
page: page,
|
||||||
|
cargoId: keyword,
|
||||||
|
partyId: partyFilter?.value,
|
||||||
|
status: boxStatusFilter,
|
||||||
|
direction: 'desc',
|
||||||
|
sort: 'id',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
dependencies: [page, boxStatusFilter, keyword, partyFilter],
|
||||||
|
selectData(data) {
|
||||||
|
return data.data.data;
|
||||||
|
},
|
||||||
|
onSuccess: data => {
|
||||||
|
// Only update data if the page or filters have changed
|
||||||
|
if (page === 1) {
|
||||||
|
setAllData(data.data.data.data);
|
||||||
|
} else {
|
||||||
|
// Prevent duplicate data
|
||||||
|
setAllData(prev => {
|
||||||
|
const existingIds = new Set(prev.map(box => box.id));
|
||||||
|
const newData = data.data.data.data.filter(box => !existingIds.has(box.id));
|
||||||
|
return [...prev, ...newData];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setHasMore(data.data.data.data.length === pageSize);
|
||||||
|
setIsFetching(false);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Secondary list query
|
||||||
|
const getListQuery = useRequest(
|
||||||
|
() =>
|
||||||
|
item_requests.getAll({
|
||||||
|
page: page,
|
||||||
|
trekId: trackId,
|
||||||
|
packetId: boxFilter?.value,
|
||||||
|
partyId: partyFilter?.value,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
dependencies: [page, trackId, boxFilter?.value, partyFilter?.value],
|
||||||
|
selectData(data) {
|
||||||
|
return data.data.data;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Form state for item amounts
|
||||||
|
const [values, setValues] = useState<{ [trackId: string]: number | '' }>({});
|
||||||
|
|
||||||
|
const handleAmountChange = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, max: number, trackId: string) => {
|
||||||
|
const val = Number(event.target.value);
|
||||||
|
if (val >= 1 && val <= max) {
|
||||||
|
setValues(prev => ({ ...prev, [trackId]: val }));
|
||||||
|
} else if (event.target.value === '') {
|
||||||
|
setValues(prev => ({ ...prev, [trackId]: '' }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loading = getBoxesQuery.loading;
|
||||||
|
|
||||||
|
// Page change handler
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(newPage: number) => {
|
||||||
|
if (!isFetching && hasMore) {
|
||||||
|
setIsFetching(true);
|
||||||
|
setPage(newPage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isFetching, hasMore]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset all filters
|
||||||
|
const resetFilter = useCallback(() => {
|
||||||
|
setPage(1);
|
||||||
|
setKeyword('');
|
||||||
|
setBoxStatusFilter(undefined);
|
||||||
|
setPartyFilter(undefined);
|
||||||
|
setAllData([]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Box options for select
|
||||||
|
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],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (boxId && defaultPartyOptions && defaultPartyOptions.length > 0) {
|
||||||
|
const selected = defaultPartyOptions.find(p => p.value === Number(boxId));
|
||||||
|
if (selected) {
|
||||||
|
setPartyFilter(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [boxId, defaultPartyOptions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (boxesIdAccepted) {
|
||||||
|
navigation.push(`${pageLinks.dashboard.acceptance.index}?boxId=${boxesIdAccepted}`);
|
||||||
|
}
|
||||||
|
}, [boxesIdAccepted]);
|
||||||
|
|
||||||
|
const onChangePrint = async (id: number, newStatus: PrintStatus) => {
|
||||||
|
if (changeStatusIds.includes(id)) return;
|
||||||
|
try {
|
||||||
|
setChangeStatusIds(p => [...p, id]);
|
||||||
|
const res = await box_requests.find({ packetId: id });
|
||||||
|
await box_requests.update({
|
||||||
|
cargoId: String(res.data.data.packet.cargoId),
|
||||||
|
items: res.data.data.items,
|
||||||
|
print: newStatus === 'true' ? true : false,
|
||||||
|
packetId: String(res.data.data.packet.id),
|
||||||
|
passportId: res.data.data.client.passportId,
|
||||||
|
status: res.data.data.packet.status,
|
||||||
|
});
|
||||||
|
setPrintStatuses(prev => ({
|
||||||
|
...prev,
|
||||||
|
[id]: newStatus,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
} finally {
|
||||||
|
setChangeStatusIds(prev => prev.filter(i => i !== id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter changes with debouncing
|
||||||
|
useEffect(() => {
|
||||||
|
const debounceTimer = setTimeout(() => {
|
||||||
|
if (page === 1) {
|
||||||
|
getBoxesQuery.refetch();
|
||||||
|
} else {
|
||||||
|
setPage(1); // This will trigger the refetch automatically
|
||||||
|
}
|
||||||
|
}, 350);
|
||||||
|
|
||||||
|
return () => clearTimeout(debounceTimer);
|
||||||
|
}, [keyword, partyFilter?.value, boxFilter?.value]);
|
||||||
|
|
||||||
|
// Fetch box amounts only when needed
|
||||||
|
useEffect(() => {
|
||||||
|
if (allData.length === 0 || loading) return;
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const fetchAmounts = async () => {
|
||||||
|
const result: Record<number, { totalAmount: number; totalAccepted: number }> = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
allData.map(async box => {
|
||||||
|
if (boxAmounts[box.id]) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await box_requests.find({ packetId: box.id });
|
||||||
|
const boxData = res.data.data;
|
||||||
|
|
||||||
|
const total = boxData.items.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
acc.totalAmount = boxData.packet.totalItems ?? 0;
|
||||||
|
if (item.acceptedNumber && item.acceptedNumber > 0) {
|
||||||
|
acc.totalAccepted += 1;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ totalAmount: 0, totalAccepted: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
result[box.id] = total;
|
||||||
|
} catch (e) {
|
||||||
|
if (!controller.signal.aborted) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Object.keys(result).length > 0) {
|
||||||
|
setBoxAmounts(prev => ({ ...prev, ...result }));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAmounts();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controller.abort();
|
||||||
|
};
|
||||||
|
}, [allData, loading]);
|
||||||
|
|
||||||
|
// Optimized scroll handler with throttling
|
||||||
|
const handleScroll = useCallback(() => {
|
||||||
|
if (!tableContainerRef.current || isFetching || !hasMore) return;
|
||||||
|
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = tableContainerRef.current;
|
||||||
|
const scrollThreshold = 100; // pixels from bottom to trigger load
|
||||||
|
|
||||||
|
if (scrollHeight - (scrollTop + clientHeight) < scrollThreshold) {
|
||||||
|
handleChange(page + 1);
|
||||||
|
}
|
||||||
|
}, [page, isFetching, hasMore, handleChange]);
|
||||||
|
|
||||||
|
// Scroll event listener with cleanup
|
||||||
|
useEffect(() => {
|
||||||
|
const container = tableContainerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const throttledScroll = throttle(handleScroll, 200);
|
||||||
|
container.addEventListener('scroll', throttledScroll);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
container.removeEventListener('scroll', throttledScroll);
|
||||||
|
};
|
||||||
|
}, [handleScroll]);
|
||||||
|
|
||||||
|
// Box options for select
|
||||||
|
const boxOptions = (inputValue: string) => {
|
||||||
|
return box_requests
|
||||||
|
.getAll({
|
||||||
|
cargoId: inputValue,
|
||||||
|
partyId: partyFilter?.value,
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
return res.data.data.data.map(p => ({ label: p.name, value: p.id }));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset box filter when party changes
|
||||||
|
useEffect(() => {
|
||||||
|
setBoxFilter(undefined);
|
||||||
|
}, [partyFilter]);
|
||||||
|
|
||||||
|
// Table columns definition
|
||||||
|
const columns: ColumnData<IBox>[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
label: t('No'),
|
||||||
|
width: 100,
|
||||||
|
renderCell(data, rowIndex) {
|
||||||
|
return rowIndex + 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'partyName',
|
||||||
|
label: t('party_name'),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'name',
|
||||||
|
label: t('name'),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'packetNetWeight',
|
||||||
|
label: t('weight'),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'totalItems',
|
||||||
|
label: t('count_of_items'),
|
||||||
|
width: 120,
|
||||||
|
renderCell: data => {
|
||||||
|
const total = boxAmounts[data.id];
|
||||||
|
|
||||||
|
if (!total) return <Typography>...</Typography>;
|
||||||
|
const isCompleted = total.totalAmount === total.totalAccepted && total.totalAmount > 0;
|
||||||
|
return (
|
||||||
|
<Stack direction='row' alignItems='center' spacing={1}>
|
||||||
|
<Typography>
|
||||||
|
{data.totalItems} / {total.totalAccepted}
|
||||||
|
</Typography>
|
||||||
|
{isCompleted && <CheckCircle sx={{ color: '#22c55e', fontSize: 16 }} />}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'totalNetWeight',
|
||||||
|
label: t('party_weight'),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'cargoId',
|
||||||
|
label: t('cargo_id'),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'passportName',
|
||||||
|
label: t('client'),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'id',
|
||||||
|
label: t('print'),
|
||||||
|
width: 120,
|
||||||
|
renderHeaderCell() {
|
||||||
|
return (
|
||||||
|
<Stack direction={'row'} alignItems={'center'}>
|
||||||
|
<span>{t('print')}</span>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
renderCell(data) {
|
||||||
|
const total = boxAmounts[data.id];
|
||||||
|
const isCompleted = total?.totalAccepted === total?.totalAmount && total?.totalAmount > 0;
|
||||||
|
return (
|
||||||
|
<Button onClick={() => onPrintBox(data)}>
|
||||||
|
<Print className='h-3 w-3 mr-1' />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'status',
|
||||||
|
label: t('status'),
|
||||||
|
width: 240,
|
||||||
|
renderHeaderCell() {
|
||||||
|
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;
|
||||||
|
onChangePrint(data.id, newValue);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem value='true'>Chop etildi</MenuItem>
|
||||||
|
<MenuItem value='false'>Chop etilmadi</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
width: 100,
|
||||||
|
numeric: true,
|
||||||
|
renderCell(data) {
|
||||||
|
return (
|
||||||
|
<ActionPopMenu
|
||||||
|
buttons={[
|
||||||
|
{
|
||||||
|
icon: <RemoveRedEye sx={{ path: { color: '#3489E4' } }} />,
|
||||||
|
label: t('view_packet'),
|
||||||
|
onClick: () => {
|
||||||
|
navigation.push(pageLinks.dashboard.boxes.detail(data.id));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[t, boxAmounts, printStatuses, navigation, onPrintBox, onChangePrint]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Form handling for items
|
||||||
|
const [items, setItems] = useState<Product>();
|
||||||
|
const [loaer, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<Product>({
|
||||||
|
defaultValues: {
|
||||||
|
trekId: items?.trekId,
|
||||||
|
name: items?.name,
|
||||||
|
nameRu: items?.nameRu,
|
||||||
|
amount: items?.amount,
|
||||||
|
weight: items?.weight,
|
||||||
|
acceptedNumber: Number(values),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateItems = async (item: Product, acceptedNumber: number) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const updateBody: UpdateProductBodyType = {
|
||||||
|
itemId: item.id,
|
||||||
|
acceptedNumber: item.amount,
|
||||||
|
amount: item.amount,
|
||||||
|
name: item.name,
|
||||||
|
nameRu: item.nameRu,
|
||||||
|
trekId: item.trekId,
|
||||||
|
weight: item.weight,
|
||||||
|
};
|
||||||
|
await item_requests.update(updateBody);
|
||||||
|
getListQuery.refetch();
|
||||||
|
setValues(prev => ({ ...prev, [item.trekId]: '' }));
|
||||||
|
setTrackId('');
|
||||||
|
} catch (error) {
|
||||||
|
notifyUnknownError(error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<CustomModal open={isModalOpen} onClose={closeModal} title={t('product_inspection')}>
|
||||||
|
<Box sx={style}>
|
||||||
|
<Typography id='modal-modal-title' variant='h6' component='h2'>
|
||||||
|
{t('product_inspection')}
|
||||||
|
</Typography>
|
||||||
|
<Typography id='modal-modal-description' sx={{ mt: 2 }}>
|
||||||
|
{t('enter_product')}
|
||||||
|
</Typography>
|
||||||
|
<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);
|
||||||
|
navigation.push(pageLinks.dashboard.boxes.detail(newValue.value));
|
||||||
|
}}
|
||||||
|
styles={selectDefaultStyles}
|
||||||
|
noOptionsMessage={() => t('enter_box_name_to_find')}
|
||||||
|
loadingMessage={() => t('loading')}
|
||||||
|
defaultOptions={defaultBoxOptions!}
|
||||||
|
loadOptions={boxOptions}
|
||||||
|
placeholder={t('filter_box_name')}
|
||||||
|
/>
|
||||||
|
<Button onClick={() => closeModal()} sx={{ position: 'absolute', right: '10px' }}>
|
||||||
|
<CloseIcon />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</CustomModal>
|
||||||
|
|
||||||
|
<Stack direction={'row'} mb={3} spacing={3}>
|
||||||
|
<Button onClick={() => openModal()}>{t('product_inspection')}</Button>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
width={1}
|
||||||
|
mb={3}
|
||||||
|
sx={{
|
||||||
|
padding: '28px',
|
||||||
|
borderRadius: '16px',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack mb={3.5} direction={'row'} justifyContent={'space-between'} alignItems={'center'}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: '20px',
|
||||||
|
lineHeight: '24px',
|
||||||
|
fontWeight: 600,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
color: '#000',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('packet')}
|
||||||
|
</Typography>
|
||||||
|
<Stack direction={'row'} alignItems={'center'} spacing={2}>
|
||||||
|
<BaseInput
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: <Search color='primary' />,
|
||||||
|
}}
|
||||||
|
placeholder={t('filter_packet_name')}
|
||||||
|
value={keyword}
|
||||||
|
onChange={e => {
|
||||||
|
setKeyword(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AsyncSelect
|
||||||
|
isClearable
|
||||||
|
value={partyFilter}
|
||||||
|
onChange={(newValue: any) => {
|
||||||
|
setPartyFilter(newValue);
|
||||||
|
if (newValue) {
|
||||||
|
setBoxIdAccepted(newValue.value);
|
||||||
|
navigation.push(`${pageLinks.dashboard.acceptance.index}?boxId=${newValue.value}`);
|
||||||
|
} else {
|
||||||
|
setBoxIdAccepted(undefined);
|
||||||
|
navigation.push(`${pageLinks.dashboard.acceptance.index}`);
|
||||||
|
}
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
styles={selectDefaultStyles}
|
||||||
|
noOptionsMessage={() => t('not_found')}
|
||||||
|
loadingMessage={() => t('loading')}
|
||||||
|
defaultOptions={defaultPartyOptions!}
|
||||||
|
loadOptions={partyOptions}
|
||||||
|
placeholder={t('filter_party_name')}
|
||||||
|
/>
|
||||||
|
<BaseButton colorVariant='gray' startIcon={<FilterListOff />} size='small' onClick={resetFilter}>
|
||||||
|
{t('reset_filter')}
|
||||||
|
</BaseButton>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
mb={6}
|
||||||
|
ref={tableContainerRef}
|
||||||
|
sx={{
|
||||||
|
height: 'calc(100vh - 300px)',
|
||||||
|
overflowY: 'auto',
|
||||||
|
'&::-webkit-scrollbar': {
|
||||||
|
width: '6px',
|
||||||
|
},
|
||||||
|
'&::-webkit-scrollbar-thumb': {
|
||||||
|
backgroundColor: '#888',
|
||||||
|
borderRadius: '3px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MyTable columns={columns} data={allData} loading={loading && page === 1} />
|
||||||
|
{isFetching && page > 1 && (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
|
||||||
|
<CircularProgress size={24} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
{selectedBoxDetails && (
|
||||||
|
<div style={{ display: 'none' }}>
|
||||||
|
<BoxesPrintList ref={printRef} boxData={selectedBoxDetails} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple throttle implementation
|
||||||
|
function throttle<T extends (...args: any[]) => any>(func: T, limit: number): T {
|
||||||
|
let inThrottle: boolean;
|
||||||
|
return function (this: any, ...args: any[]) {
|
||||||
|
if (!inThrottle) {
|
||||||
|
func.apply(this, args);
|
||||||
|
inThrottle = true;
|
||||||
|
setTimeout(() => (inThrottle = false), limit);
|
||||||
|
}
|
||||||
|
} as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DashboardAcceptancePage;
|
||||||
@@ -70,8 +70,6 @@ const DashboardEditBoxPage = (props: Props) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(getOneBox, 'pkets');
|
|
||||||
|
|
||||||
if (getOneBox.loading || !getOneBox.data) {
|
if (getOneBox.loading || !getOneBox.data) {
|
||||||
return <Loader p={8} size={96} />;
|
return <Loader p={8} size={96} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,23 @@
|
|||||||
import Loader from '@/components/common/Loader';
|
import Loader from '@/components/common/Loader';
|
||||||
import BaseInput from '@/components/ui-kit/BaseInput';
|
import BaseInput from '@/components/ui-kit/BaseInput';
|
||||||
import { useAuthContext } from '@/context/auth-context';
|
import { useAuthContext } from '@/context/auth-context';
|
||||||
import { BoxStatus } from '@/data/box/box.model';
|
import { BoxStatus, PrintStatus } from '@/data/box/box.model';
|
||||||
import { box_requests } from '@/data/box/box.requests';
|
import { box_requests } from '@/data/box/box.requests';
|
||||||
|
import { Product, UpdateProductBodyType } from '@/data/item/item.mode';
|
||||||
|
import { item_requests } from '@/data/item/item.requests';
|
||||||
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||||
import useRequest from '@/hooks/useRequest';
|
import useRequest from '@/hooks/useRequest';
|
||||||
|
import { useModalStore } from '@/modalStorage/modalSlice';
|
||||||
|
import { notifyUnknownError } from '@/services/notification';
|
||||||
|
import { Print, Search } from '@mui/icons-material';
|
||||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||||
import DangerousIcon from '@mui/icons-material/Dangerous';
|
import DangerousIcon from '@mui/icons-material/Dangerous';
|
||||||
import { Box, Divider, Grid, Stack, Typography, styled } from '@mui/material';
|
import { Box, Button, Card, CardContent, Divider, FormControl, Grid, MenuItem, Select, Stack, Typography, styled } from '@mui/material';
|
||||||
|
import { CloseIcon } from 'next/dist/client/components/react-dev-overlay/internal/icons/CloseIcon';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useReactToPrint } from 'react-to-print';
|
||||||
|
import CustomModal from '../customModal/CustomModal';
|
||||||
|
|
||||||
const StyledViewBox = styled(Box)`
|
const StyledViewBox = styled(Box)`
|
||||||
.item-row {
|
.item-row {
|
||||||
@@ -30,15 +38,52 @@ const StyledViewBox = styled(Box)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 400,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '10px',
|
||||||
|
p: 4,
|
||||||
|
};
|
||||||
|
|
||||||
const DashboardBoxesOnePage = () => {
|
const DashboardBoxesOnePage = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
const { isOpen: isModalOpen, openModal, closeModal } = useModalStore();
|
||||||
const box_id = params.box_id as string;
|
const box_id = params.box_id as string;
|
||||||
const { isAdmin: isAdminUser } = useAuthContext();
|
const { isAdmin: isAdminUser } = useAuthContext();
|
||||||
const t = useMyTranslation();
|
const t = useMyTranslation();
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [trackId, setTrackId] = useState<string>();
|
||||||
|
const [values, setValues] = useState<{ [trackId: string]: number | '' }>({});
|
||||||
|
const [selectedBoxDetails, setSelectedBoxDetails] = useState<any>(null);
|
||||||
|
const printRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [printStatus, setPrintStatus] = useState<PrintStatus>('false');
|
||||||
|
const [changeStatusIds, setChangeStatusIds] = useState<number[]>([]);
|
||||||
|
|
||||||
const { data: boxData, loading } = useRequest(() => box_requests.find({ packetId: box_id }), {
|
const handlePrint = useReactToPrint({
|
||||||
|
contentRef: printRef,
|
||||||
|
onAfterPrint: () => {
|
||||||
|
setSelectedBoxDetails(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: boxData,
|
||||||
|
loading,
|
||||||
|
refetch,
|
||||||
|
} = useRequest(() => box_requests.find({ packetId: box_id }), {
|
||||||
selectData(data) {
|
selectData(data) {
|
||||||
const boxData = data.data.data;
|
const boxData = data.data.data;
|
||||||
|
const currentPrintStatus = boxData.packet.print ? 'true' : 'false';
|
||||||
|
|
||||||
|
setPrintStatus(currentPrintStatus);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: +box_id,
|
id: +box_id,
|
||||||
@@ -49,6 +94,7 @@ const DashboardBoxesOnePage = () => {
|
|||||||
box_size: boxData.packet.volume,
|
box_size: boxData.packet.volume,
|
||||||
passportName: boxData.packet.passportName,
|
passportName: boxData.packet.passportName,
|
||||||
status: boxData.packet.status,
|
status: boxData.packet.status,
|
||||||
|
print: boxData.packet.print,
|
||||||
packetId: box_id,
|
packetId: box_id,
|
||||||
partyId: +boxData.packet.partyId,
|
partyId: +boxData.packet.partyId,
|
||||||
partyName: boxData.packet.partyName,
|
partyName: boxData.packet.partyName,
|
||||||
@@ -88,6 +134,132 @@ const DashboardBoxesOnePage = () => {
|
|||||||
return statuses;
|
return statuses;
|
||||||
}, [isAdminUser, t]);
|
}, [isAdminUser, t]);
|
||||||
|
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const getListQuery = useRequest(
|
||||||
|
() =>
|
||||||
|
item_requests.getAll({
|
||||||
|
page: page,
|
||||||
|
trekId: trackId,
|
||||||
|
packetId: boxData?.packetId,
|
||||||
|
partyId: boxData?.partyId,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
dependencies: [page, trackId, boxData],
|
||||||
|
selectData(data) {
|
||||||
|
return data.data.data;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const onPrintBox = async (boxData: any) => {
|
||||||
|
try {
|
||||||
|
const response = await box_requests.find({ packetId: boxData.id });
|
||||||
|
const boxOne = response.data.data;
|
||||||
|
const detailedBoxData = {
|
||||||
|
id: +boxData.id,
|
||||||
|
box_name: boxOne.packet.name,
|
||||||
|
net_weight: +boxOne.packet.brutto,
|
||||||
|
box_weight: +boxOne.packet.boxWeight,
|
||||||
|
box_type: boxOne.packet.boxType,
|
||||||
|
box_size: boxOne.packet.volume,
|
||||||
|
passportName: boxOne.packet.passportName,
|
||||||
|
status: boxOne.packet.status,
|
||||||
|
packetId: boxData.id,
|
||||||
|
partyId: +boxOne.packet.partyId,
|
||||||
|
partyName: boxOne.packet.partyName,
|
||||||
|
passportId: boxOne.client?.passportId,
|
||||||
|
client_id: boxOne.packet?.cargoId,
|
||||||
|
clientName: boxOne.client?.passportName,
|
||||||
|
products_list: boxOne.items.map((item: any) => ({
|
||||||
|
id: item.id,
|
||||||
|
price: item.price,
|
||||||
|
cargoId: item.cargoId,
|
||||||
|
trekId: item.trekId,
|
||||||
|
name: item.name,
|
||||||
|
nameRu: item.nameRu,
|
||||||
|
amount: +item.amount,
|
||||||
|
acceptedNumber: item.acceptedNumber,
|
||||||
|
weight: +item.weight,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
setSelectedBoxDetails(detailedBoxData);
|
||||||
|
setTimeout(() => {
|
||||||
|
handlePrint();
|
||||||
|
}, 100);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch box details:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangePrint = async (newStatus: PrintStatus) => {
|
||||||
|
if (!boxData || changeStatusIds.includes(boxData.id)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setChangeStatusIds(p => [...p, boxData.id]);
|
||||||
|
const res = await box_requests.find({ packetId: boxData.id });
|
||||||
|
await box_requests.update({
|
||||||
|
cargoId: String(res.data.data.packet.cargoId),
|
||||||
|
items: res.data.data.items,
|
||||||
|
print: newStatus === 'true',
|
||||||
|
packetId: String(res.data.data.packet.id),
|
||||||
|
passportId: res.data.data.client.passportId,
|
||||||
|
status: res.data.data.packet.status,
|
||||||
|
});
|
||||||
|
setPrintStatus(newStatus);
|
||||||
|
await refetch();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Print status update failed:', error);
|
||||||
|
} finally {
|
||||||
|
setChangeStatusIds(prev => prev.filter(i => i !== boxData.id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [loaer, setLoading] = useState(false);
|
||||||
|
const updateItems = async (item: Product, acceptedNumber: number) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const updateBody: UpdateProductBodyType = {
|
||||||
|
itemId: item.id,
|
||||||
|
acceptedNumber: item.amount,
|
||||||
|
amount: item.amount,
|
||||||
|
name: item.name,
|
||||||
|
nameRu: item.nameRu,
|
||||||
|
trekId: item.trekId,
|
||||||
|
weight: item.weight,
|
||||||
|
};
|
||||||
|
await item_requests.update(updateBody);
|
||||||
|
getListQuery.refetch();
|
||||||
|
await Promise.all([getListQuery.refetch(), refetch()]);
|
||||||
|
setValues(prev => ({ ...prev, [item.trekId]: '' }));
|
||||||
|
setTrackId('');
|
||||||
|
} catch (error) {
|
||||||
|
notifyUnknownError(error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!trackId) return;
|
||||||
|
|
||||||
|
const delayDebounce = setTimeout(() => {
|
||||||
|
const item = getListQuery.data?.data?.[0];
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
const accepted = item.acceptedNumber === item.amount;
|
||||||
|
if (!accepted) {
|
||||||
|
updateItems(item, item.amount).then(() => {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setTrackId('');
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => clearTimeout(delayDebounce);
|
||||||
|
}, [trackId, getListQuery.data]);
|
||||||
|
|
||||||
if (loading || !boxData) {
|
if (loading || !boxData) {
|
||||||
return <Loader p={8} size={96} />;
|
return <Loader p={8} size={96} />;
|
||||||
}
|
}
|
||||||
@@ -102,6 +274,73 @@ const DashboardBoxesOnePage = () => {
|
|||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Stack direction={'row'} mb={3} spacing={3}>
|
||||||
|
<Button onClick={() => openModal()}>{t('product_inspection')}</Button>
|
||||||
|
</Stack>
|
||||||
|
<CustomModal open={isModalOpen} onClose={closeModal} title={t('product_inspection')}>
|
||||||
|
<Box sx={style}>
|
||||||
|
<Typography id='modal-modal-title' variant='h6' component='h2'>
|
||||||
|
{t('product_inspection')}
|
||||||
|
</Typography>
|
||||||
|
<Typography id='modal-modal-description' sx={{ mt: 2 }}>
|
||||||
|
{t('enter_product')}
|
||||||
|
</Typography>
|
||||||
|
<BaseInput
|
||||||
|
inputRef={inputRef}
|
||||||
|
autoFocus
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: <Search color='primary' />,
|
||||||
|
}}
|
||||||
|
value={trackId}
|
||||||
|
onChange={e => {
|
||||||
|
setTrackId(e.target.value);
|
||||||
|
}}
|
||||||
|
placeholder={t('filter_item_name')}
|
||||||
|
/>
|
||||||
|
{trackId && trackId.length > 0 && (
|
||||||
|
<>
|
||||||
|
{getListQuery.loading ? (
|
||||||
|
<Typography sx={{ mt: 2 }}>{t('loading')}...</Typography>
|
||||||
|
) : getListQuery.data?.data && getListQuery.data?.data.length > 0 ? (
|
||||||
|
getListQuery.data?.data.map(e => (
|
||||||
|
<Box key={e.id} sx={{ width: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Card sx={{ minWidth: 275, mb: 2 }}>
|
||||||
|
<CardContent>
|
||||||
|
<Typography sx={{ fontSize: 14 }}>
|
||||||
|
{t('track_id')}: {e.trekId}
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ fontSize: 14 }}>Nomi: {e.name || e.nameRu}</Typography>
|
||||||
|
<Typography sx={{ fontSize: 14 }}>Mahsulot soni: {e.amount}</Typography>
|
||||||
|
<Typography sx={{ fontSize: 14 }}>Paket nomi: {e?.packetName}</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Button
|
||||||
|
sx={{ mt: '10px' }}
|
||||||
|
onClick={() => {
|
||||||
|
if (values[e.trekId] !== '') {
|
||||||
|
updateItems(e, Number(values[e.trekId]));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('confirmation')}
|
||||||
|
</Button>
|
||||||
|
<audio src='/sounds/success-sound.wav' autoPlay style={{ display: 'none' }}></audio>
|
||||||
|
</Box>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Typography sx={{ mt: 2 }}>{t('not_found') || 'Mahsulot topilmadi'}</Typography>
|
||||||
|
<audio src='/sounds/not_found.wav' autoPlay style={{ display: 'none' }}></audio>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button onClick={() => closeModal()} sx={{ position: 'absolute', right: '10px' }}>
|
||||||
|
<CloseIcon />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</CustomModal>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant='h5' mb={3.5}>
|
<Typography variant='h5' mb={3.5}>
|
||||||
{t('view_packet')}
|
{t('view_packet')}
|
||||||
@@ -134,7 +373,12 @@ const DashboardBoxesOnePage = () => {
|
|||||||
{boxData.passportName || '-'}
|
{boxData.passportName || '-'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={12} sx={{ justifyContent: 'flex-end' }}>
|
||||||
|
<Typography sx={{ textAlign: 'end' }}>
|
||||||
|
{boxData.products_list.length} /
|
||||||
|
{boxData.products_list.filter(product => product.acceptedNumber && product.acceptedNumber > 0).length}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Stack
|
<Stack
|
||||||
sx={{
|
sx={{
|
||||||
@@ -145,9 +389,6 @@ const DashboardBoxesOnePage = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{boxData.products_list.map((product, index: number) => {
|
{boxData.products_list.map((product, index: number) => {
|
||||||
//
|
|
||||||
|
|
||||||
//
|
|
||||||
let totalPrice = 0;
|
let totalPrice = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -237,10 +478,90 @@ const DashboardBoxesOnePage = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: '20px' }}>
|
||||||
|
<Button onClick={() => onPrintBox(boxData)}>
|
||||||
|
<Print className='h-3 w-3 mr-1' />
|
||||||
|
</Button>
|
||||||
|
<FormControl
|
||||||
|
sx={{
|
||||||
|
m: 1,
|
||||||
|
border: 'none',
|
||||||
|
minWidth: 120,
|
||||||
|
background: printStatus === 'true' ? '#3489E4' : '#DF2F99',
|
||||||
|
}}
|
||||||
|
size='small'
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
labelId={`print-status-${boxData.id}`}
|
||||||
|
id={`print-status-${boxData.id}`}
|
||||||
|
sx={{ color: 'white', border: 'none' }}
|
||||||
|
value={printStatus}
|
||||||
|
onChange={async e => {
|
||||||
|
const newValue = e.target.value as PrintStatus;
|
||||||
|
await onChangePrint(newValue);
|
||||||
|
}}
|
||||||
|
disabled={changeStatusIds.includes(boxData.id)}
|
||||||
|
>
|
||||||
|
<MenuItem value='true'>Chop etildi</MenuItem>
|
||||||
|
<MenuItem value='false'>Chop etilmadi</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Hidden print component */}
|
||||||
|
{selectedBoxDetails && (
|
||||||
|
<div style={{ display: 'none' }}>
|
||||||
|
<div ref={printRef}>
|
||||||
|
<Box sx={{ padding: 2 }}>
|
||||||
|
<Typography variant='h4' gutterBottom>
|
||||||
|
Box Details
|
||||||
|
</Typography>
|
||||||
|
<Typography variant='h6' gutterBottom>
|
||||||
|
Box Name: {selectedBoxDetails.box_name}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant='body1' gutterBottom>
|
||||||
|
Party: {selectedBoxDetails.partyName}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant='body1' gutterBottom>
|
||||||
|
Client: {selectedBoxDetails.clientName}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ marginTop: 2 }}>
|
||||||
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{ border: '1px solid #ddd', padding: '8px' }}>ID</th>
|
||||||
|
<th style={{ border: '1px solid #ddd', padding: '8px' }}>Name</th>
|
||||||
|
<th style={{ border: '1px solid #ddd', padding: '8px' }}>Amount</th>
|
||||||
|
<th style={{ border: '1px solid #ddd', padding: '8px' }}>Accepted</th>
|
||||||
|
<th style={{ border: '1px solid #ddd', padding: '8px' }}>Weight</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{selectedBoxDetails.products_list.map((product: any) => (
|
||||||
|
<tr key={product.id}>
|
||||||
|
<td style={{ border: '1px solid #ddd', padding: '8px' }}>{product.trekId}</td>
|
||||||
|
<td style={{ border: '1px solid #ddd', padding: '8px' }}>
|
||||||
|
{product.name || product.nameRu}
|
||||||
|
</td>
|
||||||
|
<td style={{ border: '1px solid #ddd', padding: '8px' }}>{product.amount}</td>
|
||||||
|
<td style={{ border: '1px solid #ddd', padding: '8px' }}>{product.acceptedNumber}</td>
|
||||||
|
<td style={{ border: '1px solid #ddd', padding: '8px' }}>{product.weight}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</StyledViewBox>
|
</StyledViewBox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import useInput from '@/hooks/useInput';
|
|||||||
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
||||||
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||||
import useRequest from '@/hooks/useRequest';
|
import useRequest from '@/hooks/useRequest';
|
||||||
|
import { useBoxIdStore } from '@/modalStorage/partyId';
|
||||||
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';
|
||||||
@@ -37,6 +38,7 @@ import {
|
|||||||
Search,
|
Search,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { Box, Button, Card, CardContent, FormControl, MenuItem, Modal, Select, Stack, Typography } from '@mui/material';
|
import { Box, Button, Card, CardContent, FormControl, MenuItem, Modal, Select, Stack, Typography } from '@mui/material';
|
||||||
|
import { useSearchParams } from 'next/navigation';
|
||||||
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';
|
||||||
@@ -80,6 +82,9 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
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>>({});
|
const [printStatuses, setPrintStatuses] = useState<Record<number, string>>({});
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const boxId = searchParams.get('boxId');
|
||||||
|
const { boxesId, setBoxId } = useBoxIdStore();
|
||||||
|
|
||||||
// Print uchun state
|
// Print uchun state
|
||||||
const [selectedBoxForPrint, setSelectedBoxForPrint] = useState<IBox | null>(null);
|
const [selectedBoxForPrint, setSelectedBoxForPrint] = useState<IBox | null>(null);
|
||||||
@@ -95,7 +100,6 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
setSelectedBoxDetails(null);
|
setSelectedBoxDetails(null);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: defaultPartyOptions } = useRequest(() => party_requests.getAll({}), {
|
const { data: defaultPartyOptions } = useRequest(() => party_requests.getAll({}), {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
selectData(data) {
|
selectData(data) {
|
||||||
@@ -114,7 +118,6 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
try {
|
try {
|
||||||
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;
|
||||||
|
|
||||||
const detailedBoxData = {
|
const detailedBoxData = {
|
||||||
id: +boxData.id,
|
id: +boxData.id,
|
||||||
box_name: boxOne.packet.name,
|
box_name: boxOne.packet.name,
|
||||||
@@ -142,7 +145,6 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
weight: +item.weight,
|
weight: +item.weight,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
setSelectedBoxDetails(detailedBoxData);
|
setSelectedBoxDetails(detailedBoxData);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
handlePrint();
|
handlePrint();
|
||||||
@@ -151,7 +153,6 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
console.error('Failed to fetch box details:', error);
|
console.error('Failed to fetch box details:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpen = (data: any) => {
|
const handleOpen = (data: any) => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
};
|
};
|
||||||
@@ -222,6 +223,22 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (boxId && defaultPartyOptions && defaultPartyOptions.length > 0) {
|
||||||
|
const selected = defaultPartyOptions.find(p => p.value === Number(boxId));
|
||||||
|
if (selected) {
|
||||||
|
setPartyFilter(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [boxId, defaultPartyOptions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (boxesId) {
|
||||||
|
navigation.push(`${pageLinks.dashboard.boxes.index}?boxId=${boxesId}`);
|
||||||
|
console.log(boxesId, 'useeffect');
|
||||||
|
}
|
||||||
|
}, [boxesId]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: list,
|
data: list,
|
||||||
totalElements,
|
totalElements,
|
||||||
@@ -346,9 +363,11 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
const boxData = res.data.data;
|
const boxData = res.data.data;
|
||||||
|
|
||||||
const total = boxData.items.reduce(
|
const total = boxData.items.reduce(
|
||||||
(acc: { totalAmount: number; totalAccepted: number }, item: any) => {
|
(acc, item) => {
|
||||||
acc.totalAmount += +item.amount || 0;
|
acc.totalAmount = boxData.packet.totalItems ?? 0;
|
||||||
acc.totalAccepted += +item.acceptedNumber || 0;
|
if (item.acceptedNumber && item.acceptedNumber > 0) {
|
||||||
|
acc.totalAccepted += 1;
|
||||||
|
}
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{ totalAmount: 0, totalAccepted: 0 }
|
{ totalAmount: 0, totalAccepted: 0 }
|
||||||
@@ -434,7 +453,7 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Stack direction='row' alignItems='center' spacing={1}>
|
<Stack direction='row' alignItems='center' spacing={1}>
|
||||||
<Typography>
|
<Typography>
|
||||||
{total.totalAmount} | {total.totalAccepted}
|
{data.totalItems} | {total.totalAccepted}
|
||||||
</Typography>
|
</Typography>
|
||||||
{isCompleted && <CheckCircle sx={{ color: '#22c55e', fontSize: 16 }} />}
|
{isCompleted && <CheckCircle sx={{ color: '#22c55e', fontSize: 16 }} />}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -828,6 +847,13 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
value={partyFilter}
|
value={partyFilter}
|
||||||
onChange={(newValue: any) => {
|
onChange={(newValue: any) => {
|
||||||
setPartyFilter(newValue);
|
setPartyFilter(newValue);
|
||||||
|
if (newValue) {
|
||||||
|
setBoxId(newValue.value);
|
||||||
|
navigation.push(`${pageLinks.dashboard.boxes.index}?boxId=${newValue.value}`);
|
||||||
|
} else {
|
||||||
|
setBoxId(undefined);
|
||||||
|
navigation.push(`${pageLinks.dashboard.boxes.index}`);
|
||||||
|
}
|
||||||
setPage(1);
|
setPage(1);
|
||||||
}}
|
}}
|
||||||
styles={selectDefaultStyles}
|
styles={selectDefaultStyles}
|
||||||
|
|||||||
44
src/routes/private/customModal/CustomModal.tsx
Normal file
44
src/routes/private/customModal/CustomModal.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Close as CloseIcon } from '@mui/icons-material';
|
||||||
|
import { Box, Button, Typography } from '@mui/material';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
type CustomModalProps = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
children: ReactNode;
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CustomModal({ open, onClose, children, title }: CustomModalProps) {
|
||||||
|
if (!open) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* <Backdrop open={true} sx={{ border: '1px solid red' }} onClick={onClose} /> */}
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'fixed',
|
||||||
|
zIndex: 1300,
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 2,
|
||||||
|
boxShadow: 24,
|
||||||
|
width: 400,
|
||||||
|
p: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<Typography variant='h6'>{title}</Typography>
|
||||||
|
<Button onClick={onClose}>
|
||||||
|
<CloseIcon />
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box mt={2}>{children}</Box>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -86,7 +86,6 @@ 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,
|
||||||
|
|||||||
@@ -129,12 +129,10 @@ const DashboardRealBoxesPage = (props: Props) => {
|
|||||||
try {
|
try {
|
||||||
setDownloadIds(p => [...p, id]);
|
setDownloadIds(p => [...p, id]);
|
||||||
const response = await real_box_requests.downloadQrCode({ boxId: id });
|
const response = await real_box_requests.downloadQrCode({ boxId: id });
|
||||||
console.log(response, 'rres');
|
|
||||||
const file = new File([response.data], 'qr.png', { type: response.data.type });
|
const file = new File([response.data], 'qr.png', { type: response.data.type });
|
||||||
file_service.download(file);
|
file_service.download(file);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifyUnknownError(error);
|
notifyUnknownError(error);
|
||||||
console.log(error);
|
|
||||||
} finally {
|
} finally {
|
||||||
setDownloadIds(prev => prev.filter(i => i !== id));
|
setDownloadIds(prev => prev.filter(i => i !== id));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user