Compare commits

...

58 Commits

Author SHA1 Message Date
Samandar Turgunboyev
dc80d43e15 added cargoType 2025-08-25 19:03:15 +05:00
Azizbek Usmonov
7c47c1e942 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!55
2025-07-17 10:52:18 +05:00
Azizbek Usmonov
59b0dd2633 Merge branch 'samandar' into 'dev'
401 error

See merge request azizziy/cpost!54
2025-07-17 10:51:50 +05:00
Samandar Turg'unboev
3c0e1b3407 401 error 2025-07-17 10:48:09 +05:00
Azizbek Usmonov
03bd0a65c0 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!53
2025-07-12 18:28:57 +05:00
Azizbek Usmonov
a3f80b84dd Merge branch 'samandar' into 'dev'
added page acceptance

See merge request azizziy/cpost!52
2025-07-12 18:28:40 +05:00
Samandar Turg'unboev
6b48f507a4 added page acceptance 2025-07-12 18:11:24 +05:00
Azizbek Usmonov
f04f06606f Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!51
2025-06-30 18:00:15 +05:00
Azizbek Usmonov
99722792bf Merge branch 'samandar' into 'dev'
url build

See merge request azizziy/cpost!50
2025-06-30 17:59:56 +05:00
Samandar Turg'unboev
62a5c062d1 url build 2025-06-30 17:57:55 +05:00
Azizbek Usmonov
1ea62c42a4 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!49
2025-06-30 16:57:38 +05:00
Azizbek Usmonov
cae0de23b1 Merge branch 'samandar' into 'dev'
url

See merge request azizziy/cpost!48
2025-06-30 16:54:27 +05:00
Azizbek Usmonov
15997a48c5 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!47
2025-06-30 16:01:02 +05:00
Azizbek Usmonov
5e4ac84799 Merge branch 'samandar' into 'dev'
box one

See merge request azizziy/cpost!46
2025-06-30 16:00:44 +05:00
Azizbek Usmonov
9c6f06b994 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!45
2025-06-30 13:06:59 +05:00
Azizbek Usmonov
e07268fce8 Merge branch 'samandar' into 'dev'
sounds

See merge request azizziy/cpost!44
2025-06-30 13:06:41 +05:00
Azizbek Usmonov
1b42f52c92 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!43
2025-06-30 11:44:50 +05:00
Azizbek Usmonov
fe6e611475 Merge branch 'samandar' into 'dev'
filter

See merge request azizziy/cpost!42
2025-06-30 11:44:28 +05:00
Azizbek Usmonov
a08f09ec0d Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!41
2025-06-30 10:03:25 +05:00
Azizbek Usmonov
b3f8840687 Merge branch 'samandar' into 'dev'
print

See merge request azizziy/cpost!40
2025-06-30 10:03:02 +05:00
Azizbek Usmonov
bca596fdfe Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!39
2025-06-26 17:06:17 +05:00
Azizbek Usmonov
20015f5a8e Merge branch 'samandar' into 'dev'
Samandar

See merge request azizziy/cpost!38
2025-06-26 17:05:59 +05:00
Azizbek Usmonov
7939e06f0a Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!37
2025-06-25 17:27:20 +05:00
Azizbek Usmonov
e536d6c73e Merge branch 'samandar' into 'dev'
Samandar

See merge request azizziy/cpost!36
2025-06-25 17:27:02 +05:00
Azizbek Usmonov
70f7cf7f35 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!35
2025-06-24 09:57:49 +05:00
Azizbek Usmonov
e70f363dc0 Merge branch 'main' into 'dev'
# Conflicts:
#   package.json
2025-06-24 09:56:34 +05:00
Azizbek Usmonov
6d2020db85 Merge branch 'samandar' into 'dev'
Samandar

See merge request azizziy/cpost!34
2025-06-24 09:54:35 +05:00
Azizbek Usmonov
faf591ae22 Merge branch 'samandar' into 'main'
add boxes

See merge request azizziy/cpost!32
2025-06-20 18:30:16 +05:00
Azizbek Usmonov
d906462f29 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!31
2025-06-20 14:23:22 +05:00
Azizbek Usmonov
da93fc2785 Merge branch 'samandar' into 'dev'
add boxes

See merge request azizziy/cpost!30
2025-06-20 14:23:00 +05:00
Azizbek Usmonov
6882587317 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!29
2025-06-20 11:01:27 +05:00
Azizbek Usmonov
be0e2da10d Merge branch 'samandar' into 'dev'
add boxes

See merge request azizziy/cpost!28
2025-06-20 11:00:54 +05:00
Azizbek Usmonov
c748b56c8e Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!27
2025-06-19 15:27:42 +05:00
Azizbek Usmonov
7013c3c8e9 Merge branch 'samandar' into 'dev'
added pakets

See merge request azizziy/cpost!26
2025-06-19 15:27:25 +05:00
Azizbek Usmonov
d71a69e39c Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!25
2025-06-16 09:58:31 +05:00
Azizbek Usmonov
cfd9582a8c Merge branch 'samandar' into 'dev'
filter

See merge request azizziy/cpost!24
2025-06-16 09:58:12 +05:00
Azizbek Usmonov
244fb9c376 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!23
2025-06-02 15:59:17 +05:00
Azizbek Usmonov
7d8612e15f Merge branch 'samandar' into 'dev'
pagination

See merge request azizziy/cpost!22
2025-06-02 15:58:55 +05:00
Azamov Samandar
d5c03acc6d Deploy 2025-06-01 09:48:20 +00:00
Azizbek Usmonov
7ac2613c05 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!21
2025-05-29 12:57:37 +05:00
Azizbek Usmonov
e430e42d13 Merge branch 'samandar' into 'dev'
Samandar

See merge request azizziy/cpost!20
2025-05-29 12:57:23 +05:00
Azizbek Usmonov
68e12aa56b Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!18
2025-05-29 10:36:06 +05:00
Samandar Turg'unboev
4562b4583e Merge branch 'samandar' into 'dev'
docker update

See merge request azizziy/cpost!17
2025-05-29 05:35:22 +00:00
Azizbek Usmonov
d6997f1012 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!16
2025-05-29 10:29:22 +05:00
Azizbek Usmonov
2e9674db84 Merge branch 'samandar' into 'dev'
build

See merge request azizziy/cpost!15
2025-05-29 10:29:05 +05:00
Azizbek Usmonov
ae820a288a Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!14
2025-05-29 09:53:39 +05:00
Azizbek Usmonov
c7b53c3103 Merge branch 'samandar' into 'dev'
exel

See merge request azizziy/cpost!13
2025-05-29 09:52:56 +05:00
Azizbek Usmonov
8234506796 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!12
2025-05-28 15:14:33 +05:00
Azizbek Usmonov
c9f4b578ff Merge branch 'samandar' into 'dev'
exel name

See merge request azizziy/cpost!11
2025-05-28 15:14:16 +05:00
Azizbek Usmonov
075be82d56 Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!10
2025-05-28 15:02:06 +05:00
Azizbek Usmonov
4ba4acd8d3 Merge branch 'samandar' into 'dev'
edit packet

See merge request azizziy/cpost!9
2025-05-28 15:01:48 +05:00
Azizbek Usmonov
f834fc261e Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!8
2025-05-28 10:50:06 +05:00
Azizbek Usmonov
e01f0232c1 Merge branch 'samandar' into 'dev'
exel download blob

See merge request azizziy/cpost!7
2025-05-28 10:49:48 +05:00
Azizbek Usmonov
256ac1f43f Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!6
2025-05-26 13:55:03 +05:00
Azizbek Usmonov
52b89b4bcf Merge branch 'samandar' into 'dev'
real-boxses exel download

See merge request azizziy/cpost!5
2025-05-26 13:54:16 +05:00
Azizbek Usmonov
8d6fb646cc Merge branch 'dev' into 'main'
Dev

See merge request azizziy/cpost!4
2025-05-26 09:41:20 +05:00
Azizbek Usmonov
b6d08b0237 Merge branch 'samandar' into 'dev'
realbox edit components

See merge request azizziy/cpost!3
2025-05-26 09:39:18 +05:00
Azizbek Usmonov
ab0f4311c7 Merge branch 'samandar' into 'dev'
real-boxing

See merge request azizziy/cpost!2
2025-05-24 09:33:59 +05:00
48 changed files with 3400 additions and 213 deletions

16
Makefile Normal file
View File

@@ -0,0 +1,16 @@
FILE = dc-front.yml
pull:
git fetch
git merge origin/main
down:
docker compose -f $(FILE) down
up:
docker compose -f $(FILE) up -d --build
deploy: pull down up

View File

@@ -3,10 +3,10 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "cross-env NEXT_PUBLIC_API_URL=https://cpost.felixits.uz next dev --port=3080",
"build": "cross-env NEXT_PUBLIC_API_URL=https://api.cpost-express.uz next build",
"build:dev": "cross-env NEXT_PUBLIC_API_URL=https://cpost.felixits.uz next build",
"build:prod": "cross-env NEXT_PUBLIC_API_URL=https://api.cpost-express.uz next build",
"dev": "cross-env NEXT_PUBLIC_API_URL=http://141.105.64.233:7723 next dev --port=3080",
"build": "cross-env NEXT_PUBLIC_API_URL=https://api.cpcargo.uz next build",
"build:dev": "cross-env NEXT_PUBLIC_API_URL=http://141.105.64.233:7723 next build",
"build:prod": "cross-env NEXT_PUBLIC_API_URL=https://api.cpcargo.uz next build",
"start": "next start",
"lint": "next lint",
"test": "vitest",

View File

@@ -0,0 +1,5 @@
import DashboardAcceptancePage from '@/routes/private/acceptance/DashboardAcceptancePage';
export default function Home() {
return <DashboardAcceptancePage />;
}

View File

@@ -0,0 +1,9 @@
import { CircularProgress, Stack } from '@mui/material';
export default function DashboardLoading() {
return (
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
<CircularProgress size={'64px'} />
</Stack>
);
}

View File

@@ -0,0 +1,21 @@
'use client';
import Loader from '@/components/common/Loader';
import { party_requests } from '@/data/party/party.requests';
import useRequest from '@/hooks/useRequest';
import DashboardCreateForbiddenPage from '@/routes/private/forbidden-create/DashboardCreateForbiddenPage';
export default function Home() {
const partiesData = useRequest(() => party_requests.getAll({ status: 'COLLECTING' }), {
selectData(data) {
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
},
placeholderData: [],
});
if (partiesData.loading || !partiesData.data) {
return <Loader p={8} size={96} />;
}
return <DashboardCreateForbiddenPage partiesData={partiesData.data} />;
}

View File

@@ -0,0 +1,9 @@
import { CircularProgress, Stack } from '@mui/material';
export default function DashboardLoading() {
return (
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
<CircularProgress size={'64px'} />
</Stack>
);
}

View File

@@ -0,0 +1,5 @@
import DashboardEditForbidden from '@/routes/private/forbidden-create/DashboardEditForbidden';
export default function Home() {
return <DashboardEditForbidden />;
}

View File

@@ -0,0 +1,5 @@
import DashboardForbiddenPage from '@/routes/private/forbidden';
export default function Home() {
return <DashboardForbiddenPage />;
}

View File

@@ -17,7 +17,7 @@ body {
background-color: #fff;
color: #555351;
font-family: 'Inter', 'Arial', sans-serif !important;
overflow-x: hidden;
/* overflow-x: hidden; */
}
.dashboard-layout {
@@ -43,7 +43,7 @@ body {
border: 0;
padding: 0;
clip: rect(0 0 0 0);
overflow: hidden;
/* overflow: hidden; */
}
.no-select {

View File

@@ -14,6 +14,7 @@ export interface ColumnData<Data, DataKey = keyof Data> {
getSxStyles?: (data: Data) => SxProps<Theme>;
renderCell?: (data: Data, rowIndex: number) => React.ReactNode;
renderHeaderCell?: (rowIndex: number) => React.ReactNode;
isComplated?: (data: Data, rowIndex: number) => any;
}
const StyledTable = styled(Table)`
@@ -63,17 +64,19 @@ type Props<Data> = {
loading: boolean;
onClickRow?: (data: Data) => void;
color?: string;
disableSortingAndStatusFetch?: boolean;
};
const MyTable = <Data extends { id: number | string }>(props: Props<Data>) => {
const MyTable = <Data extends { id: number | string }>({ disableSortingAndStatusFetch = true, ...props }: Props<Data>) => {
const { columns, data, loading, onClickRow } = props;
const isEmpty = !data?.length && !loading;
const [boxStatuses, setBoxStatuses] = React.useState<Record<string, boolean>>({});
// Enhanced sorting: completed boxes (green) go to the bottom
const sortedData = React.useMemo(() => {
if (disableSortingAndStatusFetch) return data;
return [...data].sort((a: any, b: any) => {
const aPrint = a.print ? 1 : 0;
const bPrint = b.print ? 1 : 0;
@@ -91,11 +94,11 @@ const MyTable = <Data extends { id: number | string }>(props: Props<Data>) => {
return Number(a.id) - Number(b.id); // fallback sort
});
}, [data, boxStatuses]);
}, [data, boxStatuses, disableSortingAndStatusFetch]);
React.useEffect(() => {
const fetchBoxStatuses = async () => {
if (!data.length || loading) return;
if (disableSortingAndStatusFetch || !data.length || loading) return;
const statuses: Record<string, boolean> = {};
@@ -131,7 +134,7 @@ const MyTable = <Data extends { id: number | string }>(props: Props<Data>) => {
};
fetchBoxStatuses();
}, [data, loading]);
}, [data, loading, disableSortingAndStatusFetch]);
return (
<Box>
@@ -168,36 +171,30 @@ const MyTable = <Data extends { id: number | string }>(props: Props<Data>) => {
</StyledTableRow>
) : (
sortedData.map((row: any, rowIndex) => {
const isCompleted = boxStatuses[row.id];
console.log(row, 'rows');
const isCompleted = columns.some(col => col.isComplated?.(row, rowIndex)) || boxStatuses[row.id];
return (
<StyledTableRow
key={row.id}
sx={{
background: row.print
? '#dbeafe' // kok rang (bg-blue-100)
? '#dbeafe'
: !row.hasInvoice
? 'inherit'
: isCompleted
? '#dcfce7' // yashil (bg-green-100)
: '#fef2f2', // qizil (bg-red-100)
? '#c5ffc0ff'
: '#fef2f2',
borderLeft: !row.hasInvoice
? 'inherit'
: row.print
? '4px solid #3b82f6' // kok chegara (border-blue-500)
? '4px solid #3b82f6'
: isCompleted
? '4px solid #22c55e' // yashil chegara
: '4px solid #ef4444', // qizil chegara
? '4px solid #22c55e'
: '4px solid #ef4444',
...(onClickRow && !row.hasInvoice
? {
cursor: 'pointer',
'&:hover': {
backgroundColor: row.print
? '#bfdbfe' // hover kok (bg-blue-200)
: isCompleted
? '#bbf7d0' // hover yashil (bg-green-200)
: '#fee2e2', // hover qizil (bg-red-200)
backgroundColor: row.print ? '#bfdbfe' : isCompleted ? '#1ea34e' : '#fee2e2',
},
}
: {}),

View File

@@ -113,4 +113,69 @@ export const routes = [
),
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],
},
// {
// title: 'Taqiqlangan buyumlar',
// path: pageLinks.dashboard.forbidden.index,
// icon: (
// <SvgIcon fontSize='small' style={{ width: '28px', height: '28px' }}>
// <svg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 24 24' fill='none'>
// <path
// d='M3.16992 7.43994L11.9999 12.5499L20.7699 7.46991'
// stroke='currentColor'
// stroke-width='1.5'
// stroke-linecap='round'
// stroke-linejoin='round'
// />
// <path d='M12 21.61V12.54' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' />
// <path
// d='M21.6101 9.17V14.83C21.6101 14.88 21.6101 14.92 21.6001 14.97C20.9001 14.36 20.0001 14 19.0001 14C18.0601 14 17.1901 14.33 16.5001 14.88C15.5801 15.61 15.0001 16.74 15.0001 18C15.0001 18.75 15.2101 19.46 15.5801 20.06C15.6701 20.22 15.7801 20.37 15.9001 20.51L14.0701 21.52C12.9301 22.16 11.0701 22.16 9.93011 21.52L4.59012 18.56C3.38012 17.89 2.39014 16.21 2.39014 14.83V9.17C2.39014 7.79 3.38012 6.11002 4.59012 5.44002L9.93011 2.48C11.0701 1.84 12.9301 1.84 14.0701 2.48L19.4101 5.44002C20.6201 6.11002 21.6101 7.79 21.6101 9.17Z'
// stroke='currentColor'
// stroke-width='1.5'
// stroke-linecap='round'
// stroke-linejoin='round'
// />
// <path
// d='M23 18C23 18.75 22.79 19.46 22.42 20.06C22.21 20.42 21.94 20.74 21.63 21C20.93 21.63 20.01 22 19 22C17.54 22 16.27 21.22 15.58 20.06C15.21 19.46 15 18.75 15 18C15 16.74 15.58 15.61 16.5 14.88C17.19 14.33 18.06 14 19 14C21.21 14 23 15.79 23 18Z'
// stroke='currentColor'
// stroke-width='1.5'
// stroke-miterlimit='10'
// stroke-linecap='round'
// stroke-linejoin='round'
// />
// <path
// d='M20.0702 19.0399L17.9502 16.9299'
// stroke='currentColor'
// stroke-width='1.5'
// stroke-miterlimit='10'
// stroke-linecap='round'
// stroke-linejoin='round'
// />
// <path
// d='M20.0502 16.96L17.9302 19.0699'
// stroke='currentColor'
// stroke-width='1.5'
// stroke-miterlimit='10'
// stroke-linecap='round'
// stroke-linejoin='round'
// />
// </svg>
// </SvgIcon>
// ),
// roles: [UserRoleEnum.ADMIN],
// },
];

View File

@@ -45,6 +45,8 @@ export default function BasePagination({ page, pageSize, totalCount, onChange }:
onChange={(_, newPage) => onChange(newPage)}
variant='outlined'
shape='rounded'
siblingCount={11}
boundaryCount={1}
color='primary'
sx={{
'.Mui-selected': {

View File

@@ -55,8 +55,11 @@ export const AuthContextProvider = (props: { children: React.ReactNode }) => {
// phone: '99894444444444444',
// },
});
} catch (err) {
} catch (err: any) {
console.error(err);
if (err.response.status === 401) {
auth_service.logout();
}
setState({
isAuth: true,
isLoading: false,

View File

@@ -36,6 +36,7 @@ export interface IBoxDetail {
volume: string;
boxWeight: number;
brutto: number;
print: PrintStatus;
hasInvoice: boolean;
status: BoxStatus;
};
@@ -55,6 +56,7 @@ export interface IBoxDetail {
amount: number;
weight: number;
acceptedNumber: number;
cargoType: 'AUTO' | 'AVIA';
price: number;
totalPrice: number;
hasImage: boolean;
@@ -69,12 +71,14 @@ export type CreateBoxBodyType = {
status: BoxStatus;
cargoId: string;
passportId: string;
cargoType: 'AUTO' | 'AVIA';
partyId: string;
items: {
trekId: string;
name: string;
amount: number;
weight: number;
cargoType: 'AUTO' | 'AVIA';
}[];
};
@@ -85,6 +89,7 @@ export type UpdateBoxBodyType = {
print?: boolean;
// clientId: number;
cargoId?: string;
cargoType: 'AUTO' | 'AVIA';
// type: string;
// name: string;
@@ -97,7 +102,7 @@ export type UpdateBoxBodyType = {
name: string;
amount: number;
weight: number;
cargoType: 'AUTO' | 'AVIA';
id: number;
price: number;
totalPrice: number;

View File

@@ -12,6 +12,7 @@ export const box_requests = {
cargoId?: string;
partyId?: string | number;
status?: BoxStatus;
cargoType?: 'AVIA' | 'AUTO';
}) {
return request.get<CommonResponseType<PageAble<IBox>>>('/packets/list', { params });
},

View File

@@ -10,6 +10,7 @@ export type Product = {
nameRu: string;
amount: number;
weight: number;
cargoType: 'AUTO' | 'AVIA';
price?: number;
packetName: string;
totalPrice?: number;
@@ -26,6 +27,7 @@ export type UpdateProductBodyType = {
itemId: string | number;
trekId: string;
name: string;
cargoType: 'AUTO' | 'AVIA';
nameRu: string;
amount: number;
weight: number;

View File

@@ -10,6 +10,7 @@ export const item_requests = {
name?: string;
trekId?: string | number;
page?: number;
cargoType?: 'AVIA' | 'AUTO';
sort?: string;
status?: BoxStatus;
direction?: 'asc' | 'desc';

View File

@@ -3,6 +3,7 @@ export type Party = {
name: string;
partyStatus: PartyStatus;
totalBoxes: number;
cargoType: 'AUTO' | 'AVIA';
};
export type PartyStatus = 'COLLECTING' | 'ON_THE_WAY' | 'IN_CUSTOMS' | 'IN_WAREHOUSE' | 'ARRIVED' | 'DELIVERED';

View File

@@ -3,19 +3,26 @@ import { CommonResponseType, PageAble } from '@/helpers/types';
import { request } from '@/services/request';
export const party_requests = {
async getAll(params?: { page?: number; sort?: string; direction?: string; partyName?: string; status?: PartyStatus }) {
async getAll(params?: {
page?: number;
sort?: string;
direction?: string;
partyName?: string;
status?: PartyStatus;
cargoType?: 'AUTO' | 'AVIA';
}) {
return request.get<CommonResponseType<PageAble<Party>>>('/parties/list', { params });
},
async changeStatus(params: { partyId: string | number; status: PartyStatus }) {
return request.put('/parties/change', undefined, { params });
},
async create(body: { name: string }) {
async create(body: { name: string; cargoType: 'AUTO' | 'AVIA' }) {
return request.post<CommonResponseType>('/parties/create', body);
},
async update(body: { id: number | string; name: string }) {
async update(body: { id: number | string; name: string; cargoType: 'AUTO' | 'AVIA' }) {
return request.put<CommonResponseType<Party[]>>(
'/parties/update',
{ name: body.name },
{ name: body.name, cargoType: body.cargoType },
{
params: {
partyId: body.id,

View File

@@ -26,6 +26,7 @@ export interface IRealBoxDetail {
export interface RealCreateBoxBodyType {
partyName: string;
cargoType: 'AUTO' | 'AVIA';
packetItemDtos: { packetId: number; itemDtos: number[] }[];
}

View File

@@ -1,8 +1,8 @@
import { IBox, UpdateBoxBodyType, IBoxDetail, BoxStatus } from '@/data/box/box.model';
import { BoxStatus } from '@/data/box/box.model';
import { CommonResponseType, PageAble } from '@/helpers/types';
import { request } from '@/services/request';
import axios from 'axios';
import { IRealBox, IRealBoxDetail, RealCreateBoxBodyType, UpdateRealBoxBodyType } from './real-box.model';
import { IRealBox, RealCreateBoxBodyType, UpdateRealBoxBodyType } from './real-box.model';
export const real_box_requests = {
async getAll(params?: {
@@ -10,9 +10,10 @@ export const real_box_requests = {
sort?: string;
direction?: string;
cargoId?: string;
boxName?: string,
boxName?: string;
partyId?: string | number;
status?: BoxStatus;
cargoType?: 'AVIA' | 'AUTO';
}) {
return request.get<CommonResponseType<PageAble<IRealBox>>>('/boxes/list', { params });
},
@@ -25,13 +26,13 @@ export const real_box_requests = {
});
},
async find(params: { boxId?: number | string }) {
return request.get<any>(`/boxes/find/${params.boxId}`,);
return request.get<any>(`/boxes/find/${params.boxId}`);
},
async delete(params: { boxId: number | string }) {
return request.delete<CommonResponseType>('/boxes/delete', { params });
},
async downloadExcel(params: { boxId: number | string }) {
return request.get<Blob>(`/boxes/download/packets/${params.boxId}`, { responseType: "blob" });
return request.get<Blob>(`/boxes/download/packets/${params.boxId}`, { responseType: 'blob' });
},
async downloadQrCode(params: { boxId: number | string }) {
return request.get(`/qr/${params.boxId}`, { responseType: 'blob' });

View File

@@ -5,4 +5,5 @@ export type CreateStaffBodyType = {
role: string;
phone: string;
address: string;
cargoType: 'AUTO' | 'AVIA';
};

View File

@@ -13,4 +13,5 @@ export type User = {
address?: string;
role: UserRoleEnum;
active: boolean;
cargoType: 'AUTO' | 'AVIA';
};

View File

@@ -1,7 +1,7 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios';
import { getCookie, deleteCookie } from 'cookies-next';
import { useRouter } from 'next/router';
import { BASE_URI } from '@/helpers/constants';
import { auth_service } from '@/services/auth';
import axios, { AxiosError, AxiosInstance } from 'axios';
import { useRouter } from 'next/router';
const authRequest: AxiosInstance = axios.create({
baseURL: BASE_URI,
@@ -27,6 +27,7 @@ authRequest.interceptors.response.use(
(error: AxiosError) => {
const router = useRouter();
if (error.response && error.response.status === 401) {
auth_service.logout();
localStorage.removeItem('token');
router.push('/login');
}

View File

@@ -24,6 +24,11 @@ export const pageLinks = {
create: '/dashboard/boxes/create',
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: {
index: '/dashboard/items',
create: '/dashboard/items/create',
@@ -32,6 +37,11 @@ export const pageLinks = {
customers: {
index: '/dashboard/customers',
},
forbidden: {
index: '/dashboard/forbidden',
create: '/dashboard/forbidden/create',
edit: (slug: string | number) => '/dashboard/forbidden/edit/' + slug,
},
staffs: {
index: '/dashboard/staffs',
},

View File

@@ -1,5 +1,6 @@
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { BASE_URL } from '@/helpers/constants';
import { auth_service } from '@/services/auth';
import axios, { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
const myAxios: AxiosInstance = axios.create({
baseURL: BASE_URL,
@@ -25,10 +26,11 @@ myAxios.interceptors.response.use(
return response.data;
},
error => {
// if (error.response?.status === 401) {
if (error.response?.status === 401) {
// localStorage.removeItem('token');
// window.location.href = '/';
// }
auth_service.logout();
}
return Promise.reject(error);
}
);

View 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 }),
}));

View 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 }),
}));

View File

@@ -0,0 +1,799 @@
'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();
const [cargoType, setCargoType] = useState<'AUTO' | 'AVIA'>('AVIA');
// 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({ cargoType }), {
enabled: true,
selectData(data) {
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
},
placeholderData: [],
dependencies: [cargoType],
});
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',
cargoType,
sort: 'id',
}),
{
dependencies: [page, cargoType, boxStatusFilter],
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,
cargoType,
partyId: partyFilter?.value,
}),
{
dependencies: [page, trackId, boxFilter?.value, partyFilter?.value, cargoType],
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,
cargoType,
}),
{
enabled: !!partyFilter,
selectData(data) {
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
},
placeholderData: [],
dependencies: [partyFilter, cargoType],
}
);
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,
cargoType: res.data.data.items[0].cargoType,
});
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,
isComplated: data => {
const total = boxAmounts[data.id];
if (!total) return <Typography>...</Typography>;
const isCompleted = total.totalAmount === total.totalAccepted && total.totalAmount > 0;
return isCompleted;
},
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;
// 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,
cargoType: res.data.data.items[0].cargoType,
});
refetch();
getBoxesQuery.refetch();
} catch (error) {
console.error('Print status update failed:', error);
}
}}
>
<MenuItem value='true'>Chop etildi</MenuItem>
<MenuItem value='false'>Chop etilmadi</MenuItem>
</Select>
</FormControl>
);
},
},
{
label: '',
width: 100,
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,
cargoType: item.cargoType,
};
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')}
/>
<FormControl size='small' sx={{ minWidth: 200 }}>
<Select
value={cargoType ?? ''}
onChange={e => {
const value = e.target.value || undefined;
setCargoType(value as 'AVIA' | 'AUTO');
setPage(1);
}}
displayEmpty
>
<MenuItem value='AVIA'>AVIA</MenuItem>
<MenuItem value='AUTO'>AUTO</MenuItem>
</Select>
</FormControl>
<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;

View File

@@ -20,7 +20,7 @@ import { useMyTranslation } from '@/hooks/useMyTranslation';
import useRequest from '@/hooks/useRequest';
import { notifyError, notifyUnknownError } from '@/services/notification';
import { AddCircleRounded, Close } from '@mui/icons-material';
import { Box, Divider, FormHelperText, Grid, Stack, Typography, styled } from '@mui/material';
import { Box, Divider, FormControl, FormHelperText, Grid, MenuItem, Select, Stack, Typography, styled } from '@mui/material';
import get from 'lodash.get';
import { useSearchParams } from 'next/navigation';
import React, { useEffect, useMemo, useRef, useState } from 'react';
@@ -67,7 +67,7 @@ type Props = {
products_list: {
id: number;
price: number | string;
cargoType: 'AUTO' | 'AVIA';
cargoId: string;
trekId: string;
name: string;
@@ -79,7 +79,7 @@ type Props = {
};
const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => {
const [cargoIdValue, setCargoIdValue] = useState<string>('');
const [cargoType, setCargoType] = useState<'AUTO' | 'AVIA'>('AVIA');
const { user, isAdmin: isAdminUser } = useAuthContext();
const editMode = !!initialValues && !!initialValues.id;
const isAdmin = isAdminUser && editMode;
@@ -193,7 +193,13 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => {
setValue('passportId', 'AA1234567');
}, [cargoId]);
const { data: defaultParties, status: defaultPartiesStatus } = useRequest(() => party_requests.getAll({ status: 'COLLECTING' }), {
const { data: defaultParties, status: defaultPartiesStatus } = useRequest(
() =>
party_requests.getAll({
status: 'COLLECTING',
cargoType: user?.role === 'ADMIN' ? cargoType : user?.cargoType || 'AVIA',
}),
{
enabled: true,
selectData(data) {
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
@@ -208,8 +214,34 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => {
helperRef.current.finished = true;
}
},
dependencies: [cargoType, initialValues],
placeholderData: [],
});
}
);
const { data: editParties } = useRequest(
() =>
party_requests.find({
partyId: initialValues?.partyId!,
}),
{
enabled: true,
selectData(data) {
return data.data.data;
},
onSuccess(data) {
if (!editMode && data?.data?.data) {
helperRef.current.settedDefaultParty = data.data.data;
setValue('partyId', data.data.data.id);
}
helperRef.current.partyFinished = true;
if (helperRef.current.clientFinished) {
helperRef.current.finished = true;
}
},
dependencies: [initialValues],
}
);
const { data: defaultClients } = useRequest(
() =>
@@ -256,6 +288,7 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => {
cargoId: values.client_id,
passportId: values.passportId?.value || values.passport_id,
status: values.status,
cargoType: editParties?.cargoType!,
packetId: initialValues?.packetId,
items: values.products_list.map((item: any) => {
const _price = +item.price ? +item.price : 0;
@@ -269,6 +302,7 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => {
nameRu: item?.nameRu,
weight: +item.weight,
amount: +item.amount,
cargoType: editParties?.cargoType!,
price: _price,
totalPrice: _total_price,
};
@@ -293,6 +327,7 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => {
const createBody: CreateBoxBodyType = {
status: values.status,
cargoId: values.cargoId,
cargoType: user?.role === 'ADMIN' ? cargoType : user?.cargoType || 'AVIA',
passportId: values.passportId.value,
partyId: values.partyId,
items: values.products_list.map((item: any) => {
@@ -301,6 +336,7 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => {
name: item.name,
weight: +item.weight,
amount: +item.amount,
cargoType: user?.role === 'ADMIN' ? cargoType : user?.cargoType || 'AVIA',
};
}),
};
@@ -403,7 +439,22 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => {
<Typography variant='h5' mb={3.5}>
{editMode ? t('update_packet') : t('create_packet')}
</Typography>
{user?.role === 'ADMIN' && (
<FormControl size='small' sx={{ minWidth: 200, marginBottom: 4 }}>
<Select
value={editMode && editParties ? editParties.cargoType : cargoType}
onChange={e => {
const value = e.target.value || undefined;
setCargoType(value as 'AVIA' | 'AUTO');
}}
displayEmpty
disabled={editMode}
>
<MenuItem value='AVIA'>AVIA</MenuItem>
<MenuItem value='AUTO'>AUTO</MenuItem>
</Select>
</FormControl>
)}
<Grid container columnSpacing={2.5} rowSpacing={3} mb={3.5}>
<Grid item xs={5}>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
@@ -414,21 +465,25 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => {
control={control}
rules={{ required: requiredText }}
render={({ field, fieldState, formState }) => {
const currentValue =
defaultParties?.find(p => p.value === field.value) ||
(editMode ? { value: initialValues.partyId, label: initialValues.partyName } : null);
return (
<AsyncSelect
onChange={(newValue: any) => {
field.onChange(newValue.value);
}}
value={currentValue}
defaultValue={
editMode
? {
value: initialValues.partyId,
label: initialValues.partyName,
}
: partiesData?.length
: defaultParties?.length
? {
value: partiesData[0].value,
label: partiesData[0].label,
value: defaultParties[0].value,
label: defaultParties[0].label,
}
: null
}

View File

@@ -55,7 +55,7 @@ const DashboardEditBoxPage = (props: Props) => {
return {
id: item.id,
price: item.price,
cargoType: item.cargoType,
cargoId: item.cargoId,
trekId: item.trekId,
name: name,
@@ -69,8 +69,7 @@ const DashboardEditBoxPage = (props: Props) => {
},
}
);
console.log(getOneBox, 'pkets');
console.log(getOneBox);
if (getOneBox.loading || !getOneBox.data) {
return <Loader p={8} size={96} />;

View File

@@ -3,15 +3,23 @@
import Loader from '@/components/common/Loader';
import BaseInput from '@/components/ui-kit/BaseInput';
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 { Product, UpdateProductBodyType } from '@/data/item/item.mode';
import { item_requests } from '@/data/item/item.requests';
import { useMyTranslation } from '@/hooks/useMyTranslation';
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 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 { 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)`
.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 params = useParams();
const { isOpen: isModalOpen, openModal, closeModal } = useModalStore();
const box_id = params.box_id as string;
const { isAdmin: isAdminUser } = useAuthContext();
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) {
const boxData = data.data.data;
const currentPrintStatus = boxData.packet.print ? 'true' : 'false';
setPrintStatus(currentPrintStatus);
return {
id: +box_id,
@@ -49,6 +94,7 @@ const DashboardBoxesOnePage = () => {
box_size: boxData.packet.volume,
passportName: boxData.packet.passportName,
status: boxData.packet.status,
print: boxData.packet.print,
packetId: box_id,
partyId: +boxData.packet.partyId,
partyName: boxData.packet.partyName,
@@ -61,6 +107,7 @@ const DashboardBoxesOnePage = () => {
cargoId: item.cargoId,
trekId: item.trekId,
name: item.name,
cargoType: item.cargoType,
nameRu: item.nameRu,
amount: +item.amount,
acceptedNumber: item.acceptedNumber,
@@ -88,6 +135,135 @@ const DashboardBoxesOnePage = () => {
return statuses;
}, [isAdminUser, t]);
const [page, setPage] = useState(1);
const getListQuery = useRequest(
() =>
item_requests.getAll({
page: page,
trekId: trackId,
packetId: boxData?.packetId,
partyId: boxData?.partyId,
cargoType: boxData?.products_list[0].cargoType,
}),
{
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,
cargoType: res.data.data.items[0].cargoType,
});
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,
cargoType: item.cargoType,
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) {
return <Loader p={8} size={96} />;
}
@@ -102,6 +278,73 @@ const DashboardBoxesOnePage = () => {
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>
<Typography variant='h5' mb={3.5}>
{t('view_packet')}
@@ -134,7 +377,12 @@ const DashboardBoxesOnePage = () => {
{boxData.passportName || '-'}
</Typography>
</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}>
<Stack
sx={{
@@ -145,9 +393,6 @@ const DashboardBoxesOnePage = () => {
}}
>
{boxData.products_list.map((product, index: number) => {
//
//
let totalPrice = 0;
try {
@@ -237,10 +482,90 @@ const DashboardBoxesOnePage = () => {
</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>
</Grid>
</Grid>
</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>
);
};

View File

@@ -16,10 +16,12 @@ 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 { useAuth } from '@/hooks/useAuth';
import useInput from '@/hooks/useInput';
import { useMyNavigation } from '@/hooks/useMyNavigation';
import { useMyTranslation } from '@/hooks/useMyTranslation';
import useRequest from '@/hooks/useRequest';
import { useBoxIdStore } from '@/modalStorage/partyId';
import { file_service } from '@/services/file-service';
import { notifyUnknownError } from '@/services/notification';
import { getStatusColor } from '@/theme/getStatusBoxStyles';
@@ -37,6 +39,7 @@ import {
Search,
} from '@mui/icons-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 { useForm } from 'react-hook-form';
import AsyncSelect from 'react-select/async';
@@ -66,6 +69,7 @@ const DashboardBoxesPage = (props: Props) => {
const handleClose = () => setOpen(false);
const t = useMyTranslation();
const navigation = useMyNavigation();
const [cargoType, setCargoType] = useState<'AUTO' | 'AVIA'>('AVIA');
const { isAdmin } = useAuthContext();
const [page, setPage] = useState(1);
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
@@ -74,12 +78,15 @@ const DashboardBoxesPage = (props: Props) => {
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 { user } = useAuth();
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 searchParams = useSearchParams();
const boxId = searchParams.get('boxId');
const { boxesId, setBoxId } = useBoxIdStore();
// Print uchun state
const [selectedBoxForPrint, setSelectedBoxForPrint] = useState<IBox | null>(null);
@@ -95,14 +102,17 @@ const DashboardBoxesPage = (props: Props) => {
setSelectedBoxDetails(null);
},
});
const { data: defaultPartyOptions } = useRequest(() => party_requests.getAll({}), {
const { data: defaultPartyOptions } = useRequest(
() => party_requests.getAll({ cargoType: user?.role === 'ADMIN' ? cargoType : user?.cargoType || 'AVIA' }),
{
enabled: true,
selectData(data) {
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
},
dependencies: [cargoType],
placeholderData: [],
});
}
);
const partyOptions = (inputValue: string) => {
return party_requests.getAll({ partyName: inputValue }).then(res => {
@@ -114,7 +124,6 @@ const DashboardBoxesPage = (props: Props) => {
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,
@@ -142,7 +151,6 @@ const DashboardBoxesPage = (props: Props) => {
weight: +item.weight,
})),
};
setSelectedBoxDetails(detailedBoxData);
setTimeout(() => {
handlePrint();
@@ -151,7 +159,6 @@ const DashboardBoxesPage = (props: Props) => {
console.error('Failed to fetch box details:', error);
}
};
const handleOpen = (data: any) => {
setOpen(true);
};
@@ -186,9 +193,10 @@ const DashboardBoxesPage = (props: Props) => {
status: boxStatusFilter,
direction: 'desc',
sort: 'id',
cargoType: user?.role === 'ADMIN' ? cargoType : user?.cargoType || 'AVIA',
}),
{
dependencies: [page, boxStatusFilter],
dependencies: [page, boxStatusFilter, cargoType, user?.cargoType],
selectData(data) {
return data.data.data;
},
@@ -222,6 +230,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, cargoType]);
useEffect(() => {
if (boxesId) {
navigation.push(`${pageLinks.dashboard.boxes.index}?boxId=${boxesId}`);
console.log(boxesId, 'useeffect');
}
}, [boxesId]);
const {
data: list,
totalElements,
@@ -346,9 +370,11 @@ const DashboardBoxesPage = (props: Props) => {
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;
(acc, item) => {
acc.totalAmount = boxData.packet.totalItems ?? 0;
if (item.acceptedNumber && item.acceptedNumber > 0) {
acc.totalAccepted += 1;
}
return acc;
},
{ totalAmount: 0, totalAccepted: 0 }
@@ -425,6 +451,13 @@ const DashboardBoxesPage = (props: Props) => {
dataKey: 'totalItems',
label: t('count_of_items'),
width: 120,
isComplated: data => {
const total = boxAmounts[data.id];
if (!total) return <Typography>...</Typography>;
const isCompleted = total.totalAmount === total.totalAccepted && total.totalAmount > 0;
return isCompleted;
},
renderCell: data => {
const total = boxAmounts[data.id];
if (!total) return <Typography>...</Typography>;
@@ -434,7 +467,7 @@ const DashboardBoxesPage = (props: Props) => {
return (
<Stack direction='row' alignItems='center' spacing={1}>
<Typography>
{total.totalAmount} | {total.totalAccepted}
{data.totalItems} | {total.totalAccepted}
</Typography>
{isCompleted && <CheckCircle sx={{ color: '#22c55e', fontSize: 16 }} />}
</Stack>
@@ -583,6 +616,7 @@ const DashboardBoxesPage = (props: Props) => {
packetId: String(res.data.data.packet.id),
passportId: res.data.data.client.passportId,
status: res.data.data.packet.status,
cargoType: res.data.data.items[0].cargoType,
});
refetch();
getBoxesQuery.refetch();
@@ -675,6 +709,7 @@ const DashboardBoxesPage = (props: Props) => {
const updateBody: UpdateProductBodyType = {
itemId: item.id,
cargoType: item.cargoType,
acceptedNumber: item.amount,
amount: item.amount,
name: item.name,
@@ -703,7 +738,7 @@ const DashboardBoxesPage = (props: Props) => {
<BaseButton colorVariant='blue' startIcon={<Add />} href={pageLinks.dashboard.boxes.create}>
{t('create_packet')}
</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'>
<Box sx={style}>
@@ -828,6 +863,13 @@ const DashboardBoxesPage = (props: Props) => {
value={partyFilter}
onChange={(newValue: any) => {
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);
}}
styles={selectDefaultStyles}
@@ -837,6 +879,22 @@ const DashboardBoxesPage = (props: Props) => {
loadOptions={partyOptions}
placeholder={t('filter_party_name')}
/>
{user?.role === 'ADMIN' && (
<FormControl size='small' sx={{ minWidth: 200 }}>
<Select
value={cargoType ?? ''}
onChange={e => {
const value = e.target.value || undefined;
setCargoType(value as 'AVIA' | 'AUTO');
setPage(1);
}}
displayEmpty
>
<MenuItem value='AVIA'>AVIA</MenuItem>
<MenuItem value='AUTO'>AUTO</MenuItem>
</Select>
</FormControl>
)}
<BaseButton colorVariant='gray' startIcon={<FilterListOff />} size='small' onClick={resetFilter}>
{t('reset_filter')}
</BaseButton>

View 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>
</>
);
}

View File

@@ -0,0 +1,773 @@
'use client';
import BaseButton from '@/components/ui-kit/BaseButton';
import BaseIconButton from '@/components/ui-kit/BaseIconButton';
import BaseInput from '@/components/ui-kit/BaseInput';
import BaseReactSelect, { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect';
import { useAuthContext } from '@/context/auth-context';
import { BoxStatus, CreateBoxBodyType, UpdateBoxBodyType } from '@/data/box/box.model';
import { box_requests } from '@/data/box/box.requests';
import { Customer } from '@/data/customers/customer.model';
import { customer_requests } from '@/data/customers/customer.requests';
import { item_requests } from '@/data/item/item.requests';
import { Party } from '@/data/party/party.model';
import { party_requests } from '@/data/party/party.requests';
import { Passport } from '@/data/passport/passport.model';
import { passport_requests } from '@/data/passport/passport.request';
import { pageLinks } from '@/helpers/constants';
import { useMyNavigation } from '@/hooks/useMyNavigation';
import { useMyTranslation } from '@/hooks/useMyTranslation';
import useRequest from '@/hooks/useRequest';
import { notifyError, notifyUnknownError } from '@/services/notification';
import { AddCircleRounded, Close } from '@mui/icons-material';
import { Box, Divider, FormHelperText, Grid, Stack, Typography, styled } from '@mui/material';
import get from 'lodash.get';
import { useSearchParams } from 'next/navigation';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Controller, useFieldArray, useForm } from 'react-hook-form';
import AsyncSelect from 'react-select/async';
const StyledCreateBox = styled(Box)`
.item-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.item-row-field {
}
& > * {
flex: 1 1 1;
}
`;
type Props = {
partiesData?: { value: number; label: string }[];
initialValues?: {
id: number;
box_name: string;
net_weight: number;
box_weight: number;
box_type: string;
box_size: string;
status: BoxStatus;
packetId: string;
passportName: string;
passportId: number;
partyId: number;
partyName: string;
clientId: number;
client_id: string;
clientName: string;
products_list: {
id: number;
price: number | string;
cargoId: string;
trekId: string;
name: string;
nameRu: string;
amount: number;
weight: number;
}[];
};
};
const DashboardCreateForbiddenPage = ({ initialValues, partiesData }: Props) => {
const [cargoIdValue, setCargoIdValue] = useState<string>('');
const { user, isAdmin: isAdminUser } = useAuthContext();
const editMode = !!initialValues && !!initialValues.id;
const isAdmin = isAdminUser && editMode;
const t = useMyTranslation();
const params = useSearchParams();
const navigation = useMyNavigation();
const [cargoType, setCargoType] = useState<'AUTO' | 'AVIA'>('AVIA');
const helperRef = useRef<{
finished: boolean;
partyFinished: boolean;
clientFinished: boolean;
settedDefaultParty: Party | null;
settedDefaultClient: Customer | null;
// settedDefaultPartyValue: { value: number; label: string }[] | null;
}>({
settedDefaultParty: null,
settedDefaultClient: null,
partyFinished: false,
clientFinished: false,
finished: false,
// settedDefaultPartyValue: partiesData?.[0] || null,
});
const {
register,
control,
handleSubmit,
watch,
setValue,
formState: { errors },
} = useForm<any>({
defaultValues: {
partyId: params.get('party_id') ? +params.get('party_id')! : '',
box_weight: 0.9,
box_type: 'KG',
box_size: '50x40x40',
status: 'READY_TO_INVOICE',
cargoId: initialValues?.client_id,
passportId: { value: initialValues?.passportId },
...(editMode
? {}
: {
products_list: [
{
id: '',
cargoId: '',
trekId: '',
name: '',
nameRu: '',
amount: '',
weight: '',
price: '',
},
],
}),
...initialValues,
},
});
const [loading, setLoading] = useState(false);
const products = useFieldArray({
control,
name: 'products_list',
keyName: 'key',
});
const controlledProductFields = watch('products_list');
const partyIdValue = watch('partyId');
const clientIdValue = watch('client_id');
const cargoId = watch('cargoId');
const requiredText = t('required');
const [selectedPassport, setSelectedPassport] = useState<Passport | null>(null); // Tanlangan passportni saqlash uchun state (tipi Passport yoki null)
const passportOptionsInitial = initialValues?.passportId &&
initialValues?.passportName && [
{
value: initialValues?.passportId,
label: initialValues?.passportName,
},
];
const n = '123ds';
n.toUpperCase();
const { data: passportOptions } = useRequest(() => passport_requests.getAll({ cargoId: cargoId?.toUpperCase() }), {
enabled: !!cargoId,
selectData: data => {
// Ma'lumotlarni BaseReactSelect uchun mos formatga o'tkazish
const passportOptions = data.data.data.map((passport: Passport) => ({
// data.data endi Passport[]
value: passport.id, // passport id sini value sifatida
label: passport.fullName, // fullName ni label sifatida
}));
const passportId = watch('passportId');
if (!passportId && initialValues?.passportId && cargoId === initialValues?.client_id) {
const currentOption = passportOptions?.find((item: { value: number }) => item.value === initialValues?.passportId);
setValue('passportId', currentOption);
}
return passportOptions;
},
dependencies: [cargoId],
placeholderData: [], // Kerak emas, chunki server PageAble qaytarmayapti
onSuccess(data) {
if (data?.data.data?.[0]?.id) {
setValue('passportId', initialValues?.passportId);
setValue('passport_id', data.data.data[0].id);
setSelectedPassport(data.data.data[0]); // Birinchi elementni tanlash
}
},
});
useEffect(() => {
setValue('passportId', 'AA1234567');
}, [cargoId]);
const { data: editParties } = useRequest(
() =>
party_requests.find({
partyId: initialValues?.partyId!,
}),
{
enabled: true,
selectData(data) {
return data.data.data;
},
onSuccess(data) {
if (!editMode && data?.data?.data) {
helperRef.current.settedDefaultParty = data.data.data;
setValue('partyId', data.data.data.id);
}
helperRef.current.partyFinished = true;
if (helperRef.current.clientFinished) {
helperRef.current.finished = true;
}
},
dependencies: [initialValues],
}
);
const { data: defaultParties, status: defaultPartiesStatus } = useRequest(() => party_requests.getAll({ status: 'COLLECTING' }), {
enabled: true,
selectData(data) {
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
},
onSuccess(data) {
if (!editMode && data?.data?.data?.data?.[0]) {
helperRef.current.settedDefaultParty = data.data.data.data[0];
setValue('partyId', data.data.data.data[0].id);
}
helperRef.current.partyFinished = true;
if (helperRef.current.clientFinished) {
helperRef.current.finished = true;
}
},
placeholderData: [],
});
const { data: defaultClients } = useRequest(
() =>
customer_requests.getAll({
page: 1,
}),
{
enabled: !!partyIdValue,
selectData(data) {
return data.data.data.data.map(p => ({ value: p.aviaCargoId, label: p.fullName }));
// return data.data.data.map(p => ({ value: p.id, label: p.fullName }));
},
dependencies: [partyIdValue],
onSuccess(data) {
if (!editMode && !clientIdValue && data?.data?.data?.data?.[0]) {
helperRef.current.settedDefaultClient = data.data.data?.data?.[0];
setValue('client_id', data.data.data.data[0].aviaCargoId);
}
helperRef.current.clientFinished = true;
if (helperRef.current.partyFinished) {
helperRef.current.finished = true;
}
},
placeholderData: [],
}
);
const onPassportChange = (newValue: Passport | null) => {
// Tipi Passport | null
setSelectedPassport(newValue);
if (newValue) {
setValue('passport_id', newValue.id || null);
} else {
setValue('passport_id', null);
}
};
const onSubmit = handleSubmit(async values => {
try {
setLoading(true);
if (editMode) {
const updateBody: UpdateBoxBodyType = {
cargoId: values.client_id,
passportId: values.passportId?.value || values.passport_id,
status: values.status,
cargoType: editParties?.cargoType!,
packetId: initialValues?.packetId,
items: values.products_list.map((item: any) => {
const _price = +item.price ? +item.price : 0;
const _amount = +item.amount ? +item.amount : 0;
const _total_price = _price ? _price * _amount : 0;
return {
id: item.id,
trekId: item.trekId,
name: item.name,
nameRu: item?.nameRu,
weight: +item.weight,
amount: +item.amount,
price: _price,
totalPrice: _total_price,
};
}),
};
const item_delete_promises = initialValues.products_list
.filter(item => {
if (!updateBody.items?.find(i => String(i.id) === String(item.id))) {
return true;
} else {
return false;
}
})
.map(item => {
return item_requests.delete({ itemId: item.id });
});
await box_requests.update(updateBody);
await Promise.all(item_delete_promises);
} else {
const createBody: CreateBoxBodyType = {
status: values.status,
cargoId: values.cargoId,
cargoType: user?.role === 'ADMIN' ? cargoType : user?.cargoType || 'AVIA',
passportId: values.passportId.value,
partyId: values.partyId,
items: values.products_list.map((item: any) => {
return {
trekId: item.trekId,
name: item.name,
weight: +item.weight,
cargoType: user?.role === 'ADMIN' ? cargoType : user?.cargoType || 'AVIA',
amount: +item.amount,
};
}),
};
await box_requests.create(createBody);
}
navigation.push(pageLinks.dashboard.boxes.index);
} catch (error) {
notifyUnknownError(error);
} finally {
setLoading(false);
}
});
const partyOptions = (inputValue: string) => {
return party_requests.getAll({ status: 'COLLECTING', partyName: inputValue }).then(res => {
return res.data.data.data.map(p => ({ label: p.name, value: p.id }));
});
};
// const clientOptions = (inputValue: string) => {
// return customer_requests.getAll({ clientName: inputValue, page: 1 }).then(res => {
// return res.data.data.data.map(p => ({ label: p.fullName, value: p.id }));
// });
// };
const appendProduct = () => {
products.append({
id: '',
cargoId: '',
trekId: '',
name: '',
amount: '',
weight: '',
price: '',
totalPrice: '',
});
};
const removeProduct = (index: number) => {
products.remove(index);
};
const translateAndUpdateRussianName = async (text: string, index: number) => {
if (!text) return;
try {
// const responseText = await box_requests.translateWithGoogleApi({ text });
const responseText = await box_requests.translateWithMemoryApi({ text });
setValue(`products_list.${index}.nameRu`, responseText || '');
} catch (error) {
console.error(error);
notifyError('Translation api error');
}
};
const boxTypes = [
{
label: 'KG',
value: 'KG',
},
{
label: 'GABARIT',
value: 'GABARIT',
},
];
const boxStatuses = useMemo(() => {
const p: {
label: string;
value: BoxStatus;
}[] = [
{
label: t('READY_TO_INVOICE'),
value: 'READY_TO_INVOICE',
},
];
if (isAdmin) {
p.push({
label: t('READY'),
value: 'READY',
});
}
return p;
}, [isAdmin]);
return (
<StyledCreateBox
width={1}
mb={3}
sx={{
padding: '28px',
borderRadius: '16px',
backgroundColor: '#fff',
}}
>
<Box component={'form'} onSubmit={onSubmit}>
<Typography variant='h5' mb={3.5}>
{editMode ? t('update_packet') : t('create_packet')}
</Typography>
<Grid container columnSpacing={2.5} rowSpacing={3} mb={3.5}>
<Grid item xs={5}>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('party_name')}
</Typography>
<Controller
name='partyId'
control={control}
rules={{ required: requiredText }}
render={({ field, fieldState, formState }) => {
return (
<AsyncSelect
onChange={(newValue: any) => {
field.onChange(newValue.value);
}}
defaultValue={
editMode
? {
value: initialValues.partyId,
label: initialValues.partyName,
}
: partiesData?.length
? {
value: partiesData[0].value,
label: partiesData[0].label,
}
: null
}
styles={selectDefaultStyles}
noOptionsMessage={() => t('not_found')}
loadingMessage={() => t('loading')}
onBlur={field.onBlur}
name={field.name}
defaultOptions={defaultParties!}
loadOptions={partyOptions}
placeholder={t('enter_party_name_to_find')}
/>
);
}}
/>
{/* @ts-expect-error */}
{!!errors.partyId?.message && <FormHelperText sx={{ color: 'red' }}>{errors.partyId?.message}</FormHelperText>}
</Grid>
<Grid item xs={5}>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('status')}
</Typography>
<Controller
name='status'
control={control}
rules={{ required: requiredText }}
render={({ field, fieldState, formState }) => {
return (
<BaseReactSelect
value={boxStatuses?.find(p => p.value === field.value)}
onChange={(newValue: any) => {
field.onChange(newValue.value);
}}
onBlur={field.onBlur}
name={field.name}
options={boxStatuses}
/>
);
}}
/>
{/* @ts-expect-error */}
{!!errors.box_type?.message && <FormHelperText sx={{ color: 'red' }}>{errors.box_type?.message}</FormHelperText>}
</Grid>
<Grid item xs={5}>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('cargo_id')}
</Typography>
<BaseInput
disabled={!!initialValues}
type='text'
fullWidth
inputProps={{
step: 0.1,
}}
mainBorderColor='#D8D8D8'
placeholder={t('cargo_id')}
{...register('cargoId')}
/>
{!!errors.net_weight?.message && (
// @ts-expect-error
<FormHelperText sx={{ color: 'red' }}>{errors.net_weight?.message}</FormHelperText>
)}
</Grid>
<Grid item xs={5}>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('passport')}
</Typography>
<Controller
name='passportId'
control={control}
rules={{ required: requiredText }}
render={({ field, fieldState, formState }) => {
return (
<BaseReactSelect
// value={selectedPassport}
// onChange={onPassportChange}
// value={field.value}
{...field}
onChange={(newValue: any) => {
onPassportChange(newValue);
field.onChange(newValue);
}}
// options={passportOptions || passportOptionsInitial || []}
options={passportOptions || passportOptionsInitial || []}
// isLoading={passportLoading}
placeholder={t('passport')}
isDisabled={!clientIdValue || !!initialValues}
noOptionsMessage={() => t('not_found')}
loadingMessage={() => t('loading')}
/>
);
}}
/>
</Grid>
<Grid item xs={12}>
<Stack
sx={{
borderRadius: '8px',
border: '2px solid #3489E4',
background: '#FFF',
padding: '24px',
}}
>
{controlledProductFields.map((product: any, index: number) => {
//
//
let totalPrice = 0;
try {
const p = +product.price * +product.amount;
if (!Number.isNaN(p)) {
totalPrice = p;
}
} catch (error) {}
return (
<Box key={product.key} mb={1.5}>
<Box className='item-row' mb={1.5}>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('track_id')}
</Typography>
<BaseInput
InputProps={{
startAdornment: (
<Box
sx={{
backgroundColor: '#EBEFF5',
color: '#929191',
alignSelf: 'stretch',
height: 'auto',
display: 'flex',
alignItems: 'center',
pl: '10px',
pr: '10px',
borderRadius: '8px 0 0 8px',
}}
>
<span>ID</span>
</Box>
),
}}
fullWidth
placeholder={t('id')}
sx={{
'.MuiInputBase-root': {
paddingLeft: 0,
},
}}
{...register(`products_list.${index}.trekId`, { required: requiredText })}
/>
{!!get(errors, `products_list.${index}.trekId`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('name')}
</Typography>
<BaseInput
fullWidth
mainBorderColor='#D8D8D8'
placeholder={t('name')}
{...register(`products_list.${index}.name`, { required: requiredText })}
onBlur={event => {
translateAndUpdateRussianName(event.target.value, index);
}}
/>
{!!get(errors, `products_list.${index}.name`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{'NAME_RU'}
</Typography>
<BaseInput
// disabled
fullWidth
mainBorderColor='#D8D8D8'
placeholder={t('name')}
{...register(`products_list.${index}.nameRu`)}
/>
{!!get(errors, `products_list.${index}.name`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('quantity')}
</Typography>
<BaseInput
fullWidth
type='number'
mainBorderColor='#D8D8D8'
placeholder={t('quantity')}
{...register(`products_list.${index}.amount`, { required: requiredText })}
/>
{!!get(errors, `products_list.${index}.amount`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('weight')}
</Typography>
<BaseInput
fullWidth
type='number'
inputProps={{ step: 'any', min: 0, type: 'number' }}
mainBorderColor='#D8D8D8'
placeholder={t('weight')}
{...register(`products_list.${index}.weight`, { required: requiredText })}
/>
{!!get(errors, `products_list.${index}.amount`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
{isAdmin && (
<React.Fragment>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('weight')}
</Typography>
<Stack direction={'row'} alignItems={'center'} spacing={2.5}>
<BaseInput
fullWidth
type='text'
inputProps={{
step: 0.1,
}}
mainBorderColor='#D8D8D8'
placeholder={t('weight')}
{...register(`products_list.${index}.weight`)}
/>
</Stack>
</Box>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('price')}
</Typography>
<BaseInput
fullWidth
type='text'
inputProps={{
step: 0.1,
}}
mainBorderColor='#D8D8D8'
placeholder={t('price')}
{...register(`products_list.${index}.price`, { required: requiredText })}
/>
{!!get(errors, `products_list.${index}.price`) && (
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
)}
</Box>
<Box className='item-row-field'>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
{t('total_price')}
</Typography>
<BaseInput
fullWidth
type='number'
inputProps={{
step: 0.001,
}}
value={totalPrice}
mainBorderColor='#D8D8D8'
placeholder={t('total_price')}
// {...register(`products_list.${index}.totalPrice`, { required: requiredText })}
/>
</Box>
</React.Fragment>
)}
<Box className='item-row-field'>
<BaseIconButton
size='small'
colorVariant='icon-error'
sx={{ flexShrink: 0, height: 'auto', marginTop: 4.5 }}
onClick={() => removeProduct(index)}
>
<Close />
</BaseIconButton>
</Box>
</Box>
<Divider color='#EBEFF6' />
</Box>
);
})}
<Stack alignItems={'center'}>
<BaseButton sx={{ backgroundColor: '#239D5F' }} startIcon={<AddCircleRounded />} onClick={appendProduct}>
{t('add_more')}
</BaseButton>
</Stack>
</Stack>
</Grid>
</Grid>
<BaseButton type='submit' colorVariant='blue' loading={loading}>
{editMode ? t('update') : t('create')}
</BaseButton>
</Box>
</StyledCreateBox>
);
};
export default DashboardCreateForbiddenPage;

View File

@@ -0,0 +1,82 @@
'use client';
import Loader from '@/components/common/Loader';
import { box_requests } from '@/data/box/box.requests';
import useRequest from '@/hooks/useRequest';
import { useParams } from 'next/navigation';
import DashboardCreateForbiddenPage from './DashboardCreateForbiddenPage';
type Props = {};
const DashboardEditForbidden = () => {
const params = useParams();
const box_id = params.forbidden_id as string;
const getOneBox = useRequest(
() => {
return box_requests.find({ packetId: box_id });
},
{
selectData(data) {
const boxData = data.data.data;
return {
id: +box_id,
box_name: boxData.packet.name,
net_weight: +boxData.packet.brutto,
box_weight: +boxData.packet.boxWeight,
box_type: boxData.packet.boxType,
box_size: boxData.packet.volume,
passportName: boxData.packet.passportName,
status: boxData.packet.status,
packetId: box_id,
partyId: +boxData.packet.partyId,
partyName: boxData.packet.partyName,
// client_id: boxData.client?.passportId,
passportId: boxData.client?.passportId,
client_id: boxData.packet?.cargoId,
clientId: boxData.client?.passportId,
clientName: boxData.client?.passportName,
products_list: [
...boxData.items.map(item => {
let name = item.name;
let nameRu = item.nameRu;
// try {
// name = item.name.split(' / ')[0];
// nameRu = item.name.split(' / ')[1];
// } catch (error) {
// console.error('prepare edit values error', error);
// }
return {
id: item.id,
price: item.price,
cargoId: item.cargoId,
trekId: item.trekId,
name: name,
nameRu: nameRu,
amount: +item.amount,
weight: +item.weight,
};
}),
],
};
},
}
);
if (getOneBox.loading || !getOneBox.data) {
return <Loader p={8} size={96} />;
}
return (
<>
<DashboardCreateForbiddenPage initialValues={getOneBox.data} />
</>
);
};
export default DashboardEditForbidden;

View File

@@ -0,0 +1,705 @@
'use client';
import type React from 'react';
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 BasePagination from '@/components/ui-kit/BasePagination';
import { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect';
import { useAuthContext } from '@/context/auth-context';
import { type BoxStatus, 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';
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 { useBoxIdStore } from '@/modalStorage/partyId';
import { file_service } from '@/services/file-service';
import { notifyUnknownError } from '@/services/notification';
import { Add, CheckCircle, Delete, Edit, FilterListOff, Search } from '@mui/icons-material';
import { Box, Button, Card, CardContent, Modal, Stack, Typography } from '@mui/material';
import { useSearchParams } from 'next/navigation';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import AsyncSelect from 'react-select/async';
import { useReactToPrint } from 'react-to-print';
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 DashboardForbiddenPage = () => {
const [open, setOpen] = useState(false);
const [openModal, setOpenModal] = useState(false);
const handleClose = () => setOpen(false);
const t = useMyTranslation();
const navigation = useMyNavigation();
const { isAdmin } = useAuthContext();
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 searchParams = useSearchParams();
const boxId = searchParams.get('boxId');
const { boxesId, setBoxId } = useBoxIdStore();
// Print uchun state
const [selectedBoxForPrint, setSelectedBoxForPrint] = useState<IBox | null>(null);
const [selectedBoxDetails, setSelectedBoxDetails] = useState<any>(null);
const printRef = useRef<HTMLDivElement>(null);
// Print functionality
const handlePrint = useReactToPrint({
contentRef: printRef,
onAfterPrint: () => {
setSelectedBoxForPrint(null);
setSelectedBoxDetails(null);
},
});
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 }));
});
};
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) {
console.error('Failed to fetch box details:', error);
}
};
const handleOpen = (data: any) => {
setOpen(true);
};
const handleOpenModal = (data: any) => {
setOpenModal(true);
setSelectedBoxForPrint(data);
};
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]);
const getBoxesQuery = useRequest(
() =>
box_requests.getAll({
page: page,
cargoId: keyword,
partyId: partyFilter?.value,
status: boxStatusFilter,
direction: 'desc',
sort: 'id',
}),
{
dependencies: [page, boxStatusFilter],
selectData(data) {
return data.data.data;
},
}
);
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;
},
}
);
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]: '' }));
}
};
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 {
data: list,
totalElements,
totalPages,
} = useMemo(() => {
if (getBoxesQuery.data?.data) {
return {
data: getBoxesQuery.data.data,
totalElements: getBoxesQuery.data.totalElements,
totalPages: getBoxesQuery.data.totalPages,
};
} else {
return {
data: [],
totalElements: 0,
totalPages: 0,
};
}
}, [getBoxesQuery]);
const loading = getBoxesQuery.loading;
const handleChange = (newPage: number) => {
setTimeout(() => {
setPage(newPage);
}, 100);
};
const resetFilter = () => {
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;
try {
setDeleteIds(p => [...p, id]);
await box_requests.delete({ packetId: id });
getBoxesQuery.refetch();
} catch (error) {
notifyUnknownError(error);
} finally {
setDeleteIds(prev => prev.filter(i => i !== id));
}
};
const onDownloadExcel = async (id: number) => {
if (downloadIds.includes(id)) return;
try {
setDownloadIds(p => [...p, id]);
const response = await box_requests.downloadExcel({ packetId: id });
const file = new File([response.data], 'Box-excel.xlsx', { type: response.data.type });
file_service.download(file);
} catch (error) {
notifyUnknownError(error);
} finally {
setDownloadIds(prev => prev.filter(i => i !== id));
}
};
const onChangeStatus = async (id: number, newStatus: BoxStatus) => {
if (changeStatusIds.includes(id)) return;
try {
setChangeStatusIds(p => [...p, id]);
await box_requests.changeStatus({ packetId: id, status: newStatus });
getBoxesQuery.refetch();
} catch (error) {
notifyUnknownError(error);
} finally {
setChangeStatusIds(prev => prev.filter(i => i !== id));
}
};
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, boxFilter?.value]);
useEffect(() => {
const fetchAmounts = async () => {
const result: Record<number, { totalAmount: number; totalAccepted: number }> = {};
await Promise.all(
list.map(async box => {
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) {
console.error(`Failed to fetch box ${box.id}`, e);
}
})
);
setBoxAmounts(result);
};
if (list.length > 0 && !loading) {
fetchAmounts();
}
}, [list, loading]);
// Calculate completion statistics
const boxOptions = (inputValue: string) => {
return box_requests
.getAll({
cargoId: inputValue,
partyId: partyFilter?.value, // Bu qatorni qo'shing
})
.then(res => {
return res.data.data.data.map(p => ({ label: p.name, value: p.id }));
});
};
useEffect(() => {
setBoxFilter(undefined);
}, [partyFilter]);
const columns: ColumnData<IBox>[] = [
{
label: t('No'),
width: 100,
renderCell(data, rowIndex) {
return (page - 1) * pageSize + rowIndex + 1;
},
},
{
dataKey: 'id',
label: "Qo'shish",
width: 120,
renderCell: data => {
return (
<Button onClick={() => navigation.push(pageLinks.dashboard.forbidden.edit(data.id))}>
<Add />
</Button>
);
},
},
{
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,
},
{
label: '',
width: 100,
numeric: true,
renderCell(data, rowIndex) {
return (
<ActionPopMenu
buttons={[
{
icon: <Edit sx={{ path: { color: '#3489E4' } }} />,
label: t('edit'),
onClick: () => {
navigation.push(pageLinks.dashboard.forbidden.edit(data.id));
},
},
{
icon: <Delete sx={{ path: { color: '#3489E4' } }} />,
label: t('delete'),
onClick: () => {
onDelete(data.id);
},
dontCloseOnClick: true,
loading: deleteIds.includes(data.id),
},
]}
/>
);
},
},
];
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,
cargoType: item.cargoType,
name: item.name,
nameRu: item.nameRu,
trekId: item.trekId,
weight: item.weight,
};
await item_requests.update(updateBody);
getListQuery.refetch();
getBoxesQuery.refetch();
setValues(prev => ({ ...prev, [item.trekId]: '' }));
setTrackId('');
} catch (error) {
notifyUnknownError(error);
} finally {
setLoading(false);
}
};
return (
<Box>
<Stack direction={'row'} mb={3} spacing={3}>
<BaseButton colorVariant='blue' startIcon={<Add />} href={pageLinks.dashboard.forbidden.create}>
{t('create_packet')}
</BaseButton>
<Modal open={open} onClose={handleClose} aria-labelledby='modal-modal-title' aria-describedby='modal-modal-description'>
<Box sx={style}>
<Typography id='modal-modal-title' variant='h6' component='h2'>
{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);
}}
styles={selectDefaultStyles}
noOptionsMessage={() => t('enter_box_name_to_find')}
loadingMessage={() => t('loading')}
defaultOptions={defaultBoxOptions!}
loadOptions={boxOptions}
placeholder={t('filter_box_name')}
/>
<BaseInput
InputProps={{
startAdornment: <Search color='primary' />,
}}
value={trackId}
onChange={e => setTrackId(e.target.value)}
placeholder={t('filter_item_name')}
/>
{trackId && trackId.length > 0 && (
<>
{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>
</>
)}
</>
)}
</Box>
</Modal>
</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) {
setBoxId(newValue.value);
navigation.push(`${pageLinks.dashboard.boxes.index}?boxId=${newValue.value}`);
} else {
setBoxId(undefined);
navigation.push(`${pageLinks.dashboard.boxes.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}>
<MyTable columns={columns} data={list} loading={loading} disableSortingAndStatusFetch={false} />
</Box>
<Stack direction={'row'} justifyContent={'center'}>
<BasePagination page={page} pageSize={pageSize} totalCount={totalElements} onChange={handleChange} />
</Stack>
</Box>
</Box>
);
};
export default DashboardForbiddenPage;

View File

@@ -0,0 +1 @@
export { default } from './DashboardForbiddenPage';

View File

@@ -22,7 +22,7 @@ import EditItemModal from '@/routes/private/items/components/EditItemModal';
import { notifyUnknownError } from '@/services/notification';
import { getBoxStatusStyles, getStatusColor } from '@/theme/getStatusBoxStyles';
import { Add, Check, Circle, Delete, Edit, FilterList, FilterListOff, Search } from '@mui/icons-material';
import { Box, Stack, Typography } from '@mui/material';
import { Box, FormControl, MenuItem, Select, Stack, Typography } from '@mui/material';
import { useEffect, useMemo, useState } from 'react';
import AsyncSelect from 'react-select/async';
@@ -40,6 +40,7 @@ const DashboardGoodsPage = (props: Props) => {
const [partyFilter, setPartyFilter] = useState<{ label: string; value: number } | undefined>(undefined);
const [boxFilter, setBoxFilter] = useState<{ label: string; value: number } | undefined>(undefined);
const [boxStatusFilter, setBoxStatusFilter] = useState<BoxStatus | undefined>(undefined);
const [cargoType, setCargoType] = useState<'AUTO' | 'AVIA'>(user?.role === 'ADMIN' ? 'AVIA' : user?.cargoType || 'AUTO');
const [deleteIds, setDeleteIds] = useState<number[]>([]);
@@ -53,13 +54,14 @@ const DashboardGoodsPage = (props: Props) => {
partyId: partyFilter?.value,
trekId: trackKeyword,
direction: 'desc',
cargoType,
sort: 'id',
}),
{
selectData(data) {
return data.data.data;
},
dependencies: [page, boxStatusFilter, boxFilter, partyFilter],
dependencies: [page, boxStatusFilter, boxFilter, partyFilter, cargoType],
}
);
@@ -133,11 +135,12 @@ const DashboardGoodsPage = (props: Props) => {
}
};
const { data: defaultPartyOptions } = useRequest(() => party_requests.getAll({}), {
const { data: defaultPartyOptions } = useRequest(() => party_requests.getAll({ cargoType }), {
enabled: true,
selectData(data) {
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
},
dependencies: [cargoType],
placeholderData: [],
});
@@ -145,6 +148,7 @@ const DashboardGoodsPage = (props: Props) => {
() =>
box_requests.getAll({
partyId: partyFilter?.value,
cargoType,
}),
{
enabled: !!partyFilter,
@@ -387,7 +391,22 @@ const DashboardGoodsPage = (props: Props) => {
/>
<BaseInput value={trackKeyword} onChange={handleTrackKeyword} placeholder={t('filter_track_id')} />
{user?.role === 'ADMIN' && (
<FormControl size='small' sx={{ minWidth: 200 }}>
<Select
value={cargoType ?? ''}
onChange={e => {
const value = e.target.value || undefined;
setCargoType(value as 'AVIA' | 'AUTO');
setPage(1);
}}
displayEmpty
>
<MenuItem value='AVIA'>AVIA</MenuItem>
<MenuItem value='AUTO'>AUTO</MenuItem>
</Select>
</FormControl>
)}
<BaseButton colorVariant='gray' startIcon={<FilterListOff />} size='small' onClick={resetFilter}>
{t('reset_filter')}
</BaseButton>

View File

@@ -67,7 +67,7 @@ const EditItemModal = ({ onClose, open, onSuccess, item }: Props) => {
const onSubmit = handleSubmit(async values => {
try {
setLoading(true);
await item_requests.update({ itemId: item.id, ...values });
await item_requests.update({ itemId: item.id, cargoType: item.cargoType, ...values });
onSuccess();
} catch (error) {
notifyUnknownError(error);

View File

@@ -4,10 +4,11 @@ import BaseButton from '@/components/ui-kit/BaseButton';
import BaseInput from '@/components/ui-kit/BaseInput';
import { party_requests } from '@/data/party/party.requests';
import { pageLinks } from '@/helpers/constants';
import { useAuth } from '@/hooks/useAuth';
import useInput from '@/hooks/useInput';
import { useMyTranslation } from '@/hooks/useMyTranslation';
import { notifyUnknownError } from '@/services/notification';
import { Box, Stack, Typography } from '@mui/material';
import { Box, FormControl, MenuItem, Select, Stack, Typography } from '@mui/material';
import { useRouter } from 'next/navigation';
import React, { useState } from 'react';
import { useLocale } from 'use-intl';
@@ -16,6 +17,7 @@ type Props = {
initialValues?: {
id: number;
party_name: string;
cargoType: 'AUTO' | 'AVIA';
};
};
@@ -23,18 +25,27 @@ const DashboardCreatePartyPage = ({ initialValues }: Props) => {
const editMode = !!initialValues && !!initialValues.id;
const t = useMyTranslation();
const router = useRouter();
const { user } = useAuth();
const locale = useLocale();
const partyNameInput = useInput(editMode ? initialValues.party_name : '');
const [loading, setLoading] = useState(false);
const [cargoType, setCargoType] = useState<'AUTO' | 'AVIA'>('AVIA');
const onSubmit: React.FormEventHandler<HTMLFormElement> = async event => {
event.preventDefault();
try {
setLoading(true);
if (editMode) {
await party_requests.update({ name: partyNameInput.value, id: initialValues.id });
await party_requests.update({
name: partyNameInput.value,
id: initialValues.id,
cargoType: user?.role === 'ADMIN' ? cargoType : user?.cargoType || 'AVIA',
});
} else {
await party_requests.create({ name: partyNameInput.value });
await party_requests.create({
name: partyNameInput.value,
cargoType: user?.role === 'ADMIN' ? cargoType : user?.cargoType || 'AVIA',
});
}
router.push(`/${locale}` + pageLinks.dashboard.parties.index);
} catch (error) {
@@ -59,11 +70,29 @@ const DashboardCreatePartyPage = ({ initialValues }: Props) => {
{editMode ? t('update_party') : t('create_party')}
</Typography>
<Stack spacing={2} maxWidth={340} mb={3}>
<Stack spacing={10} mb={3} direction={'row'} alignItems={'end'}>
<div>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850'>
{t('party_name')}
</Typography>
<BaseInput placeholder={t('party_name')} value={partyNameInput.value} onChange={partyNameInput.onChange} />
</div>
{user?.role === 'ADMIN' && (
<FormControl size='small' sx={{ minWidth: 200 }}>
<Select
value={editMode ? initialValues.cargoType : cargoType}
onChange={e => {
const value = e.target.value || undefined;
setCargoType(value as 'AVIA' | 'AUTO');
}}
displayEmpty
disabled={editMode}
>
<MenuItem value='AVIA'>AVIA</MenuItem>
<MenuItem value='AUTO'>AUTO</MenuItem>
</Select>
</FormControl>
)}
</Stack>
<BaseButton type='submit' colorVariant='blue' loading={loading}>

View File

@@ -5,7 +5,6 @@ import { party_requests } from '@/data/party/party.requests';
import useRequest from '@/hooks/useRequest';
import DashboardCreatePartyPage from '@/routes/private/parties-create/DashboardCreatePartyPage';
import { useParams } from 'next/navigation';
import React from 'react';
type Props = {};
@@ -22,6 +21,7 @@ const DashboardEditPartyPage = (props: Props) => {
return {
id: +party_id,
party_name: data.data.data.name,
cargoType: data.data.data.cargoType,
};
},
}

View File

@@ -1,37 +1,36 @@
'use client';
import ActionPopMenu from '@/components/common/ActionPopMenu';
import Loader from '@/components/common/Loader';
import { MyTable, ColumnData } from '@/components/common/MyTable';
import { ColumnData, MyTable } from '@/components/common/MyTable';
import StatusChangePopup from '@/components/common/StatusChangePopup';
import BaseButton from '@/components/ui-kit/BaseButton';
import BaseIconButton from '@/components/ui-kit/BaseIconButton';
import BaseInput from '@/components/ui-kit/BaseInput';
import BasePagination from '@/components/ui-kit/BasePagination';
import BaseReactSelect from '@/components/ui-kit/BaseReactSelect';
import { useAuthContext } from '@/context/auth-context';
import { Party, PartyStatus, PartyStatusList, PartyStatusOptions } from '@/data/party/party.model';
import { Party, PartyStatus, PartyStatusList } from '@/data/party/party.model';
import { party_requests } from '@/data/party/party.requests';
import { DEFAULT_PAGE_SIZE, pageLinks } from '@/helpers/constants';
import { useAuth } from '@/hooks/useAuth';
import useInput from '@/hooks/useInput';
import { useMyNavigation } from '@/hooks/useMyNavigation';
import { useMyTranslation } from '@/hooks/useMyTranslation';
import useRequest from '@/hooks/useRequest';
import { file_service } from '@/services/file-service';
import { notifyUnknownError } from '@/services/notification';
import { getStatusColor } from '@/theme/getStatusBoxStyles';
import { Add, AddCircleOutline, Circle, Delete, Download, Edit, FilterList, FilterListOff, Search } from '@mui/icons-material';
import { Box, CircularProgress, Stack, SvgIcon, Tooltip, Typography } from '@mui/material';
import { useRouter } from 'next/navigation';
import React, { useEffect, useMemo, useState } from 'react';
import { getBoxStatusStyles } from '@/theme/getStatusBoxStyles';
import { getBoxStatusStyles, getStatusColor } from '@/theme/getStatusBoxStyles';
import { Add, Circle, Delete, Download, Edit, FilterList, FilterListOff, Search } from '@mui/icons-material';
import { Box, FormControl, MenuItem, Select, Stack, Typography } from '@mui/material';
import { useEffect, useMemo, useState } from 'react';
type Props = {};
const DashboardPartiesPage = (props: Props) => {
const t = useMyTranslation();
const navigation = useMyNavigation();
const { user } = useAuth();
const { isAdmin, isAdminOrUzbek } = useAuthContext();
const [cargoType, setCargoType] = useState<'AUTO' | 'AVIA'>('AVIA');
const [page, setPage] = useState(1);
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
const { value: keyword, onChange: handleKeyword, setValue: setKeyword } = useInput('');
@@ -56,13 +55,14 @@ const DashboardPartiesPage = (props: Props) => {
() =>
party_requests.getAll({
page: page,
cargoType: user?.role === 'ADMIN' ? cargoType : user?.cargoType,
partyName: keyword,
status: partyStatusFilter,
direction: 'desc',
sort: 'id',
}),
{
dependencies: [page, partyStatusFilter],
dependencies: [page, partyStatusFilter, cargoType],
selectData(data) {
return data.data.data;
},
@@ -383,7 +383,22 @@ const DashboardPartiesPage = (props: Props) => {
onChange={handleKeyword}
placeholder={t('filter_party_name')}
/>
{user?.role === 'ADMIN' && (
<FormControl size='small' sx={{ minWidth: 200 }}>
<Select
value={cargoType ?? ''}
onChange={e => {
const value = e.target.value || undefined;
setCargoType(value as 'AVIA' | 'AUTO');
setPage(1);
}}
displayEmpty
>
<MenuItem value='AVIA'>AVIA</MenuItem>
<MenuItem value='AUTO'>AUTO</MenuItem>
</Select>
</FormControl>
)}
<BaseButton colorVariant='gray' startIcon={<FilterListOff />} size='small' onClick={resetFilter}>
{t('reset_filter')}
</BaseButton>

View File

@@ -2,13 +2,13 @@
import BaseButton from '@/components/ui-kit/BaseButton';
import BaseIconButton from '@/components/ui-kit/BaseIconButton';
import { useAuthContext } from '@/context/auth-context';
import { box_requests } from '@/data/box/box.requests';
import { item_requests } from '@/data/item/item.requests';
import { party_requests } from '@/data/party/party.requests';
import { FormValues, RealCreateBoxBodyType, UpdateRealBoxBodyType } from '@/data/real-box/real-box.model';
import { real_box_requests } from '@/data/real-box/real-box.requests';
import { pageLinks } from '@/helpers/constants';
import { useAuth } from '@/hooks/useAuth';
import { useMyNavigation } from '@/hooks/useMyNavigation';
import { useMyTranslation } from '@/hooks/useMyTranslation';
import { notifyUnknownError } from '@/services/notification';
@@ -62,13 +62,14 @@ interface Props {
}
const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
const { user } = useAuthContext();
const { user } = useAuth();
const editMode = !!initialValues?.id;
const t = useMyTranslation();
const params = useSearchParams();
const { push } = useMyNavigation();
const [partyId, setPartyId] = useState<number | string>(initialValues?.partyId || '');
const [loading, setLoading] = useState(false);
const [cargoType, setCargoType] = useState<'AUTO' | 'AVIA'>(user?.role === 'ADMIN' ? 'AVIA' : user?.cargoType || 'AVIA');
const selectMenuProps = useMemo(
() => ({
@@ -86,7 +87,6 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
const [allPackets, setAllPackets] = useState<any[]>([]);
// Barcha mahsulotlarni barcha sahifadan yuklash uchun map
const [allItemsMap, setAllItemsMap] = useState<{ [packetId: number]: any[] }>({});
console.log(initialValues?.partyName);
const {
control,
@@ -124,7 +124,11 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
let packets: any[] = [];
let totalPages = 1;
try {
const firstRes = await box_requests.getAll({ partyId, page: 1 });
const firstRes = await box_requests.getAll({
partyId,
page: 1,
cargoType: editMode ? initialValues && initialValues?.paketIds?.[0].items[0].cargoType : cargoType,
});
const firstData = firstRes?.data?.data;
packets = firstData?.data || [];
totalPages = firstData?.totalPages || 1;
@@ -151,7 +155,11 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
let items: any[] = [];
let totalPages = 1;
try {
const firstRes = await item_requests.getAll({ packetId, page: 1 });
const firstRes = await item_requests.getAll({
packetId,
page: 1,
cargoType: editMode ? initialValues && initialValues?.paketIds?.[0].items[0].cargoType : cargoType,
});
const firstData = firstRes?.data?.data;
items = firstData?.data || [];
totalPages = firstData?.totalPages || 1;
@@ -206,8 +214,8 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
const requiredText = t('required');
const { data: parties = [], isLoading: isLoadingParties } = useQuery({
queryKey: ['parties-list', 'COLLECTING'],
queryFn: () => party_requests.getAll({ status: 'COLLECTING' }),
queryKey: ['parties-list', 'COLLECTING', cargoType],
queryFn: () => party_requests.getAll({ status: 'COLLECTING', cargoType }),
select: data =>
data.data.data.data.map((p: any) => ({
id: p.id,
@@ -232,6 +240,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
} else {
const createBody: RealCreateBoxBodyType = {
partyName: values.partyName,
cargoType,
packetItemDtos: values.packetItemDtos.map(packet => ({
packetId: packet.packetId,
itemDtos: packet.itemDtos,
@@ -514,6 +523,22 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
<Typography fontSize='18px' fontWeight={500} color='#5D5850' mb={2}>
{t('party_name')}
</Typography>
<Box display={'flex'} gap={2} flexDirection={'row'} justifyContent={'center'} alignItems={'center'}>
{user?.role === 'ADMIN' && (
<FormControl size='small' sx={{ minWidth: 200, height: '100%' }}>
<Select
value={editMode ? initialValues && initialValues?.paketIds?.[0].items[0].cargoType : cargoType}
onChange={e => {
const value = e.target.value || undefined;
setCargoType(value as 'AVIA' | 'AUTO');
}}
displayEmpty
>
<MenuItem value='AVIA'>AVIA</MenuItem>
<MenuItem value='AUTO'>AUTO</MenuItem>
</Select>
</FormControl>
)}
<FormControl fullWidth>
<InputLabel id='party-select-label'>{t('party_name')}</InputLabel>
<Controller
@@ -558,6 +583,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
/>
{!!errors.partyId && <FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>}
</FormControl>
</Box>
</Grid>
<Grid item xs={12}>
<Stack

View File

@@ -11,6 +11,7 @@ import { box_requests } from '@/data/box/box.requests';
import { IRealBox } from '@/data/real-box/real-box.model';
import { real_box_requests } from '@/data/real-box/real-box.requests';
import { DEFAULT_PAGE_SIZE, pageLinks } from '@/helpers/constants';
import { useAuth } from '@/hooks/useAuth';
import useInput from '@/hooks/useInput';
import { useMyNavigation } from '@/hooks/useMyNavigation';
import { useMyTranslation } from '@/hooks/useMyTranslation';
@@ -18,8 +19,8 @@ import useRequest from '@/hooks/useRequest';
import { file_service } from '@/services/file-service';
import { notifyUnknownError } from '@/services/notification';
import { Add, Delete, Download, Edit, FilterListOff, QrCode, Search } from '@mui/icons-material';
import { Box, Button, Stack, Typography } from '@mui/material';
import { useEffect, useMemo, useState } from 'react';
import { Box, Button, FormControl, MenuItem, Select, Stack, Typography } from '@mui/material';
import { useMemo, useState } from 'react';
type Props = {};
@@ -31,8 +32,9 @@ const DashboardRealBoxesPage = (props: Props) => {
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
const { value: keyword, onChange: handleKeyword, setValue: setKeyword } = useInput('');
const [boxStatusFilter, setBoxStatusFilter] = useState<BoxStatus | undefined>(undefined);
const { user } = useAuth();
const [deleteIds, setDeleteIds] = useState<number[]>([]);
const [cargoType, setCargoType] = useState<'AUTO' | 'AVIA'>('AVIA');
const [downloadIds, setDownloadIds] = useState<number[]>([]);
const [changeStatusIds, setChangeStatusIds] = useState<number[]>([]);
@@ -52,9 +54,10 @@ const DashboardRealBoxesPage = (props: Props) => {
status: boxStatusFilter,
direction: 'desc',
sort: 'id',
cargoType: user?.role === 'ADMIN' ? cargoType : user?.cargoType || 'AVIA',
}),
{
dependencies: [page, boxStatusFilter],
dependencies: [page, boxStatusFilter, cargoType],
selectData(data) {
return data.data.data;
},
@@ -83,9 +86,11 @@ const DashboardRealBoxesPage = (props: Props) => {
const loading = getBoxesQuery.loading;
const handleChange = (newPage: number) => {
if (getBoxesQuery.data?.totalElements !== 0) {
setTimeout(() => {
setPage(newPage);
}, 100);
}
};
const resetFilter = () => {
@@ -129,12 +134,10 @@ const DashboardRealBoxesPage = (props: Props) => {
try {
setDownloadIds(p => [...p, 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 });
file_service.download(file);
} catch (error) {
notifyUnknownError(error);
console.log(error);
} finally {
setDownloadIds(prev => prev.filter(i => i !== id));
}
@@ -154,15 +157,14 @@ const DashboardRealBoxesPage = (props: Props) => {
}
};
useEffect(() => {
const timeoutId = setTimeout(() => {
setPage(1);
getBoxesQuery.refetch();
}, 350);
return () => clearTimeout(timeoutId);
}, [keyword]);
// useEffect(() => {
// const timeoutId = setTimeout(() => {
// setPage(1);
// getBoxesQuery.refetch();
// }, 350);
// return () => clearTimeout(timeoutId);
// }, [keyword]);
// No, PartyName, PacketName, PartyTozaOg'irlik, CountOfItems, WeightOfItems, CargoID, PassportNameFamily - PacketStatusForInvoice
const columns: ColumnData<IRealBox>[] = [
{
label: t('No'),
@@ -371,7 +373,22 @@ const DashboardRealBoxesPage = (props: Props) => {
setKeyword(e.target.value);
}}
/>
{user?.role === 'ADMIN' && (
<FormControl size='small' sx={{ minWidth: 200 }}>
<Select
value={cargoType ?? ''}
onChange={e => {
const value = e.target.value || undefined;
setCargoType(value as 'AVIA' | 'AUTO');
setPage(1);
}}
displayEmpty
>
<MenuItem value='AVIA'>AVIA</MenuItem>
<MenuItem value='AUTO'>AUTO</MenuItem>
</Select>
</FormControl>
)}
<BaseButton colorVariant='gray' startIcon={<FilterListOff />} size='small' onClick={resetFilter}>
{t('reset_filter')}
</BaseButton>

View File

@@ -1,17 +1,17 @@
'use client';
import { MyTable, ColumnData } from '@/components/common/MyTable';
import { ColumnData, MyTable } from '@/components/common/MyTable';
import BaseButton from '@/components/ui-kit/BaseButton';
import BasePagination from '@/components/ui-kit/BasePagination';
import { staff_requests } from '@/data/staff/staff.requests';
import { User } from '@/data/user/user.model';
import { DEFAULT_PAGE_SIZE, pageLinks } from '@/helpers/constants';
import { DEFAULT_PAGE_SIZE } from '@/helpers/constants';
import { useMyTranslation } from '@/hooks/useMyTranslation';
import useRequest from '@/hooks/useRequest';
import CreateStaffModal from '@/routes/private/staffs/components/CreateStaffModal';
import { Add } from '@mui/icons-material';
import { Box, Stack, Typography } from '@mui/material';
import React, { useCallback, useMemo, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
type Props = {};
@@ -105,6 +105,11 @@ const DashboardStaffsPage = (props: Props) => {
label: t('role'),
width: 300,
},
{
dataKey: 'cargoType',
label: t('cargo type'),
width: 300,
},
{
dataKey: 'active',
label: t('status'),

View File

@@ -6,7 +6,7 @@ import { staff_requests } from '@/data/staff/staff.requests';
import { useMyTranslation } from '@/hooks/useMyTranslation';
import { notifyUnknownError } from '@/services/notification';
import { Box, Grid, Stack, Typography, styled } from '@mui/material';
import React, { useState } from 'react';
import { useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
const StyledBox = styled(Box)`
@@ -49,6 +49,7 @@ const CreateStaffModal = ({ onClose, open, onSuccess }: Props) => {
password: string;
fullName: string;
role: string;
cargoType: 'AUTO' | 'AVIA';
phone: string;
address: string;
}>({
@@ -84,6 +85,17 @@ const CreateStaffModal = ({ onClose, open, onSuccess }: Props) => {
// },
];
const CARGO_TYPES = [
{
label: 'AUTO',
value: 'AUTO',
},
{
label: 'AVIA',
value: 'AVIA',
},
];
return (
<BaseModal maxWidth='600px' onClose={onClose} open={open}>
<StyledBox component={'form'} onSubmit={onSubmit}>
@@ -119,6 +131,26 @@ const CreateStaffModal = ({ onClose, open, onSuccess }: Props) => {
}}
/>
</Grid>
<Grid item xs={6}>
<Typography className='label'>Cargo Type</Typography>
<Controller
name='cargoType'
control={control}
render={({ field, fieldState, formState }) => {
return (
<BaseReactSelect
value={CARGO_TYPES.find(p => p.value === field.value)}
onChange={(newValue: any) => {
field.onChange(newValue.value);
}}
onBlur={field.onBlur}
name={field.name}
options={CARGO_TYPES}
/>
);
}}
/>
</Grid>
<Grid item xs={6}>
<Typography className='label'>{t('username')}</Typography>
<BaseInput

View File

@@ -1,6 +1,7 @@
import { BROWSER_TOKEN_KEY, backendURL, isServer } from '@/services/request/constants';
import axios, { AxiosError, AxiosResponse } from 'axios';
import { getCookie } from 'cookies-next';
import { auth_service } from '../auth';
const request = axios.create({
baseURL: backendURL + '/api/v1',
@@ -31,6 +32,9 @@ request.interceptors.response.use(
return response;
},
async (error: AxiosError) => {
if (error.response?.status === 401) {
auth_service.logout();
}
return Promise.reject(error);
}
);