diff --git a/src/components/common/MyTable/MyTable.tsx b/src/components/common/MyTable/MyTable.tsx index 633a5c5..9b3b55b 100644 --- a/src/components/common/MyTable/MyTable.tsx +++ b/src/components/common/MyTable/MyTable.tsx @@ -3,7 +3,7 @@ import Loader from '@/components/common/Loader'; import { Scrollbar } from '@/components/common/Scrollbar'; 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'; export interface ColumnData { @@ -72,39 +72,65 @@ const MyTable = (props: Props) => { const [boxStatuses, setBoxStatuses] = React.useState>({}); + // 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(() => { const fetchBoxStatuses = async () => { + if (!data.length || loading) return; + const statuses: Record = {}; - await Promise.all( - data.map(async row => { - try { - const res = await box_requests.find({ packetId: row.id }); - const boxData = res.data.data; + try { + await Promise.all( + data.map(async row => { + try { + const res = await box_requests.find({ packetId: row.id }); + const boxData = res.data.data; - const total = boxData.items.reduce( - (acc: { totalAmount: number; totalAccepted: number }, item: any) => { - acc.totalAmount += +item.amount || 0; - acc.totalAccepted += +item.acceptedNumber || 0; - return acc; - }, - { totalAmount: 0, totalAccepted: 0 } - ); + const total = boxData.items.reduce( + (acc: { totalAmount: number; totalAccepted: number }, item: any) => { + acc.totalAmount += +item.amount || 0; + acc.totalAccepted += +item.acceptedNumber || 0; + return acc; + }, + { totalAmount: 0, totalAccepted: 0 } + ); - statuses[row.id] = total.totalAmount === total.totalAccepted; - } catch (error) { - console.error('Error fetching box status:', error); - statuses[row.id] = false; - } - }) - ); + // Box is complete (green) when all items are accepted + statuses[row.id] = total.totalAmount === total.totalAccepted && total.totalAmount > 0; + } catch (error) { + console.error(`Error fetching box status for ${row.id}:`, error); + 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]); return ( @@ -141,19 +167,37 @@ const MyTable = (props: Props) => { ) : ( - data.map((row: any, rowIndex) => { - const status = boxStatuses[row.id]; + sortedData.map((row: any, rowIndex) => { + const isCompleted = boxStatuses[row.id]; + console.log(row, 'rows'); return ( (({ boxData, key } }} key={key} > - + (({ boxData, key } gap: '12px', }} > - logo + logo (({ boxData, key } }} > CPOST EXPRESS CARGO - Reys: {boxData?.partyName} - {boxData?.client_id} + + Reys: {boxData?.partyName}-{boxData?.client_id} + + TEL: +(998) 90 113 44 77 - - telegram - instagram - TEL: +(998) 90 113 44 77 + + telegram + instagram - - {boxData?.products_list.map((list, index) => ( - - {list.trekId} - - ))} - + + + {boxData?.products_list.map((list, index) => ( + + {list.trekId} + + ))} ); diff --git a/src/routes/private/boxes/DashboardBoxesPage.tsx b/src/routes/private/boxes/DashboardBoxesPage.tsx index e3babcc..2840110 100644 --- a/src/routes/private/boxes/DashboardBoxesPage.tsx +++ b/src/routes/private/boxes/DashboardBoxesPage.tsx @@ -10,7 +10,7 @@ import BaseInput from '@/components/ui-kit/BaseInput'; import BasePagination from '@/components/ui-kit/BasePagination'; import { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect'; import { useAuthContext } from '@/context/auth-context'; -import { type BoxStatus, BoxStatusList, type IBox } from '@/data/box/box.model'; +import { type BoxStatus, BoxStatusList, type IBox, PrintStatus } from '@/data/box/box.model'; import { box_requests } from '@/data/box/box.requests'; import type { Product, UpdateProductBodyType } from '@/data/item/item.mode'; import { item_requests } from '@/data/item/item.requests'; @@ -23,8 +23,20 @@ import useRequest from '@/hooks/useRequest'; import { file_service } from '@/services/file-service'; import { notifyUnknownError } from '@/services/notification'; import { getStatusColor } from '@/theme/getStatusBoxStyles'; -import { Add, Circle, Delete, Download, Edit, FilterList, FilterListOff, Print, RemoveRedEye, Search } from '@mui/icons-material'; -import { Box, Button, Card, CardContent, Modal, Stack, TextField, Typography } from '@mui/material'; +import { + Add, + CheckCircle, + Circle, + Delete, + Download, + Edit, + FilterList, + FilterListOff, + Print, + RemoveRedEye, + Search, +} from '@mui/icons-material'; +import { Box, Button, Card, CardContent, FormControl, MenuItem, Modal, Select, Stack, Typography } from '@mui/material'; import { useEffect, useMemo, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import AsyncSelect from 'react-select/async'; @@ -61,11 +73,13 @@ const DashboardBoxesPage = (props: Props) => { const [boxStatusFilter, setBoxStatusFilter] = useState(undefined); const [trackId, setTrackId] = useState(); const [partyFilter, setPartyFilter] = useState<{ label: string; value: number } | undefined>(undefined); + const [boxFilter, setBoxFilter] = useState<{ label: string; value: number } | undefined>(undefined); const [deleteIds, setDeleteIds] = useState([]); const [downloadIds, setDownloadIds] = useState([]); const [changeStatusIds, setChangeStatusIds] = useState([]); const [boxAmounts, setBoxAmounts] = useState>({}); + const [printStatuses, setPrintStatuses] = useState>({}); // Print uchun state const [selectedBoxForPrint, setSelectedBoxForPrint] = useState(null); @@ -73,8 +87,6 @@ const DashboardBoxesPage = (props: Props) => { const printRef = useRef(null); - // Available status options (simulating admin/user permissions) - // Print functionality const handlePrint = useReactToPrint({ contentRef: printRef, @@ -100,7 +112,6 @@ const DashboardBoxesPage = (props: Props) => { const onPrintBox = async (boxData: IBox) => { try { - // Fetch detailed box data const response = await box_requests.find({ packetId: boxData.id }); const boxOne = response.data.data; @@ -158,6 +169,14 @@ const DashboardBoxesPage = (props: Props) => { return p; }, [isAdmin]); + const printOptions = useMemo(() => { + const p = ['false'] as PrintStatus[]; + if (isAdmin) { + p.push('false'); + } + return p; + }, [isAdmin]); + const getBoxesQuery = useRequest( () => box_requests.getAll({ @@ -181,9 +200,11 @@ const DashboardBoxesPage = (props: Props) => { item_requests.getAll({ page: page, trekId: trackId, + packetId: boxFilter?.value, + partyId: partyFilter?.value, }), { - dependencies: [page, trackId], + dependencies: [page, trackId, boxFilter?.value, partyFilter?.value], selectData(data) { return data.data.data; }, @@ -232,8 +253,24 @@ const DashboardBoxesPage = (props: Props) => { setPage(1); setKeyword(''); setBoxStatusFilter(undefined); + setPartyFilter(undefined); }; + const { data: defaultBoxOptions, refetch } = useRequest( + () => + box_requests.getAll({ + partyId: partyFilter?.value, + }), + { + enabled: !!partyFilter, + selectData(data) { + return data.data.data.data.map(p => ({ value: p.id, label: p.name })); + }, + placeholderData: [], + dependencies: [partyFilter], + } + ); + const onDelete = async (id: number) => { if (deleteIds.includes(id)) return; @@ -277,13 +314,26 @@ const DashboardBoxesPage = (props: Props) => { } }; + const onChangePrint = async (id: number, newStatus: PrintStatus) => { + if (changeStatusIds.includes(id)) return; + + try { + setChangeStatusIds(p => [...p, id]); + getBoxesQuery.refetch(); + } catch (error) { + notifyUnknownError(error); + } finally { + setChangeStatusIds(prev => prev.filter(i => i !== id)); + } + }; + useEffect(() => { const timeoutId = setTimeout(() => { setPage(1); getBoxesQuery.refetch(); }, 350); return () => clearTimeout(timeoutId); - }, [keyword, partyFilter?.value]); + }, [keyword, partyFilter?.value, boxFilter?.value]); useEffect(() => { const fetchAmounts = async () => { @@ -319,6 +369,14 @@ const DashboardBoxesPage = (props: Props) => { } }, [list, loading]); + // Calculate completion statistics + + const boxOptions = (inputValue: string) => { + return box_requests.getAll({ cargoId: inputValue }).then(res => { + return res.data.data.data.map(p => ({ label: p.name, value: p.id })); + }); + }; + const columns: ColumnData[] = [ { label: t('No'), @@ -361,10 +419,16 @@ const DashboardBoxesPage = (props: Props) => { renderCell: data => { const total = boxAmounts[data.id]; if (!total) return ...; + + const isCompleted = total.totalAmount === total.totalAccepted && total.totalAmount > 0; + return ( - - {total.totalAmount} | {total.totalAccepted} - + + + {total.totalAmount} | {total.totalAccepted} + + {isCompleted && } + ); }, }, @@ -459,13 +523,72 @@ const DashboardBoxesPage = (props: Props) => { }, renderCell(data) { const total = boxAmounts[data.id]; + const isCompleted = total?.totalAccepted === total?.totalAmount && total?.totalAmount > 0; + return ( - ); }, }, + { + dataKey: 'status', + label: t('status'), + width: 240, + renderHeaderCell(rowIndex) { + return ( + + {t('print_status')} + + ); + }, + renderCell(data) { + const currentValue = printStatuses[data.id] || (data.print ? 'true' : 'false'); + return ( + + + + ); + }, + }, { label: '', width: 100, @@ -572,6 +695,7 @@ const DashboardBoxesPage = (props: Props) => { {t('create_packet')} + @@ -580,12 +704,42 @@ const DashboardBoxesPage = (props: Props) => { {t('enter_product')} - { + setPartyFilter(newValue); + setPage(1); + }} + styles={selectDefaultStyles} + noOptionsMessage={() => t('not_found')} + loadingMessage={() => t('loading')} + defaultOptions={defaultPartyOptions!} + loadOptions={partyOptions} + placeholder={t('filter_party_name')} + /> + + { + 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')} + /> + , + }} value={trackId} onChange={e => setTrackId(e.target.value)} + placeholder={t('filter_item_name')} /> {trackId && trackId.length > 0 && ( <> diff --git a/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx b/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx index 5a4a5c8..9d243e4 100644 --- a/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx +++ b/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx @@ -86,6 +86,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { const [allPackets, setAllPackets] = useState([]); // Barcha mahsulotlarni barcha sahifadan yuklash uchun map const [allItemsMap, setAllItemsMap] = useState<{ [packetId: number]: any[] }>({}); + console.log(initialValues?.partyName); const { control, @@ -529,8 +530,17 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { field.onChange(e); handlePartyChange(e); }} - value={field.value || ''} + value={field.value || initialValues?.partyId || ''} // Bu yerda partyId ishlatish kerak disabled={isLoadingParties} + renderValue={selected => { + // Agar edit mode bo'lsa va initialValues mavjud bo'lsa + if (editMode && initialValues?.partyName && selected === initialValues?.partyId) { + return initialValues.partyName; + } + // Aks holda parties ro'yxatidan topish + const selectedParty = parties.find(p => p.id === selected); + return selectedParty ? selectedParty.name : ''; + }} > {isLoadingParties ? ( diff --git a/src/theme/getStatusBoxStyles.ts b/src/theme/getStatusBoxStyles.ts index 44157ce..aabb19b 100644 --- a/src/theme/getStatusBoxStyles.ts +++ b/src/theme/getStatusBoxStyles.ts @@ -1,7 +1,7 @@ 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) { case 'COLLECTING': { return '#FD9C2B'; @@ -24,13 +24,19 @@ export function getStatusColor(status: BoxStatus | PartyStatus) { case 'IN_CUSTOMS': { return '#C9A26E'; } + case 'false': { + return '#C9A26E'; + } + case 'true': { + return '#3489E4'; + } default: { return '#17D792'; } } } -export function getBoxStatusStyles(status: BoxStatus | PartyStatus) { +export function getBoxStatusStyles(status: BoxStatus | PartyStatus | PrintStatus) { let color = getStatusColor(status); return {