added cargoType

This commit is contained in:
Samandar Turgunboyev
2025-08-25 19:03:15 +05:00
parent 7c47c1e942
commit dc80d43e15
37 changed files with 2158 additions and 184 deletions

View File

@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "cross-env NEXT_PUBLIC_API_URL=https://cpcargo.felixits.uz next dev --port=3080",
"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=https://cpcargo.felixits.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",

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

@@ -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,35 +171,30 @@ const MyTable = <Data extends { id: number | string }>(props: Props<Data>) => {
</StyledTableRow>
) : (
sortedData.map((row: any, rowIndex) => {
const isCompleted = boxStatuses[row.id];
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

@@ -126,6 +126,56 @@ export const routes = [
</svg>
</SvgIcon>
),
roles: [UserRoleEnum.ADMIN, UserRoleEnum.CHINA_WORKER],
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

@@ -56,6 +56,7 @@ export interface IBoxDetail {
amount: number;
weight: number;
acceptedNumber: number;
cargoType: 'AUTO' | 'AVIA';
price: number;
totalPrice: number;
hasImage: boolean;
@@ -70,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';
}[];
};
@@ -86,6 +89,7 @@ export type UpdateBoxBodyType = {
print?: boolean;
// clientId: number;
cargoId?: string;
cargoType: 'AUTO' | 'AVIA';
// type: string;
// name: string;
@@ -98,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' });
@@ -64,4 +65,4 @@ export const real_box_requests = {
return response.data.responseData.translatedText;
},
};
};

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

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

@@ -74,6 +74,7 @@ const DashboardAcceptancePage = () => {
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);
@@ -91,12 +92,13 @@ const DashboardAcceptancePage = () => {
});
// Fetch party options
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 }));
},
placeholderData: [],
dependencies: [cargoType],
});
const partyOptions = (inputValue: string) => {
@@ -170,10 +172,11 @@ const DashboardAcceptancePage = () => {
partyId: partyFilter?.value,
status: boxStatusFilter,
direction: 'desc',
cargoType,
sort: 'id',
}),
{
dependencies: [page, boxStatusFilter, keyword, partyFilter],
dependencies: [page, cargoType, boxStatusFilter],
selectData(data) {
return data.data.data;
},
@@ -202,10 +205,11 @@ const DashboardAcceptancePage = () => {
page: page,
trekId: trackId,
packetId: boxFilter?.value,
cargoType,
partyId: partyFilter?.value,
}),
{
dependencies: [page, trackId, boxFilter?.value, partyFilter?.value],
dependencies: [page, trackId, boxFilter?.value, partyFilter?.value, cargoType],
selectData(data) {
return data.data.data;
},
@@ -251,6 +255,7 @@ const DashboardAcceptancePage = () => {
() =>
box_requests.getAll({
partyId: partyFilter?.value,
cargoType,
}),
{
enabled: !!partyFilter,
@@ -258,7 +263,7 @@ const DashboardAcceptancePage = () => {
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
},
placeholderData: [],
dependencies: [partyFilter],
dependencies: [partyFilter, cargoType],
}
);
@@ -289,6 +294,7 @@ const DashboardAcceptancePage = () => {
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,
@@ -436,6 +442,12 @@ const DashboardAcceptancePage = () => {
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];
@@ -512,7 +524,33 @@ const DashboardAcceptancePage = () => {
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>
@@ -579,6 +617,7 @@ const DashboardAcceptancePage = () => {
nameRu: item.nameRu,
trekId: item.trekId,
weight: item.weight,
cargoType: item.cargoType,
};
await item_requests.update(updateBody);
getListQuery.refetch();
@@ -693,6 +732,20 @@ const DashboardAcceptancePage = () => {
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>

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,23 +193,55 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => {
setValue('passportId', 'AA1234567');
}, [cargoId]);
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: 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 }));
},
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;
}
},
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,6 +69,7 @@ const DashboardEditBoxPage = (props: Props) => {
},
}
);
console.log(getOneBox);
if (getOneBox.loading || !getOneBox.data) {
return <Loader p={8} size={96} />;

View File

@@ -107,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,
@@ -142,6 +143,7 @@ const DashboardBoxesOnePage = () => {
trekId: trackId,
packetId: boxData?.packetId,
partyId: boxData?.partyId,
cargoType: boxData?.products_list[0].cargoType,
}),
{
dependencies: [page, trackId, boxData],
@@ -204,6 +206,7 @@ const DashboardBoxesOnePage = () => {
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();
@@ -225,6 +228,7 @@ const DashboardBoxesOnePage = () => {
name: item.name,
nameRu: item.nameRu,
trekId: item.trekId,
cargoType: item.cargoType,
weight: item.weight,
};
await item_requests.update(updateBody);

View File

@@ -16,6 +16,7 @@ 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';
@@ -68,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);
@@ -76,7 +78,7 @@ 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[]>([]);
@@ -100,13 +102,17 @@ const DashboardBoxesPage = (props: Props) => {
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 { 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 => {
@@ -187,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;
},
@@ -230,7 +237,7 @@ const DashboardBoxesPage = (props: Props) => {
setPartyFilter(selected);
}
}
}, [boxId, defaultPartyOptions]);
}, [boxId, defaultPartyOptions, cargoType]);
useEffect(() => {
if (boxesId) {
@@ -444,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>;
@@ -602,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();
@@ -694,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,
@@ -722,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}>
@@ -863,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,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}>
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850'>
{t('party_name')}
</Typography>
<BaseInput placeholder={t('party_name')} value={partyNameInput.value} onChange={partyNameInput.onChange} />
<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;
},
@@ -306,16 +306,16 @@ const DashboardPartiesPage = (props: Props) => {
},
...(isAdmin
? [
{
icon: <Download sx={{ path: { color: '#3489E4' } }} />,
label: t('download_all_items'),
onClick: () => {
onDownloadPartyItems(data.id);
},
dontCloseOnClick: true,
loading: downloadItemIds.includes(data.id),
},
]
{
icon: <Download sx={{ path: { color: '#3489E4' } }} />,
label: t('download_all_items'),
onClick: () => {
onDownloadPartyItems(data.id);
},
dontCloseOnClick: true,
loading: downloadItemIds.includes(data.id),
},
]
: []),
{
icon: <Download sx={{ path: { color: '#3489E4' } }} />,
@@ -328,16 +328,16 @@ const DashboardPartiesPage = (props: Props) => {
},
...(Boolean(data.partyStatus === 'ON_THE_WAY' || data.partyStatus === 'ARRIVED')
? [
{
icon: <Download sx={{ path: { color: '#3489E4' } }} />,
label: t('download_excel'),
onClick: () => {
onDownloadExcel(data.id);
},
loading: downloadIds.includes(data.id),
dontCloseOnClick: true,
},
]
{
icon: <Download sx={{ path: { color: '#3489E4' } }} />,
label: t('download_excel'),
onClick: () => {
onDownloadExcel(data.id);
},
loading: downloadIds.includes(data.id),
dontCloseOnClick: true,
},
]
: []),
]}
/>
@@ -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(
() => ({
@@ -123,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;
@@ -150,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;
@@ -205,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,
@@ -231,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,
@@ -513,50 +523,67 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
<Typography fontSize='18px' fontWeight={500} color='#5D5850' mb={2}>
{t('party_name')}
</Typography>
<FormControl fullWidth>
<InputLabel id='party-select-label'>{t('party_name')}</InputLabel>
<Controller
name='partyId'
control={control}
rules={{ required: requiredText }}
render={({ field }) => (
<Box display={'flex'} gap={2} flexDirection={'row'} justifyContent={'center'} alignItems={'center'}>
{user?.role === 'ADMIN' && (
<FormControl size='small' sx={{ minWidth: 200, height: '100%' }}>
<Select
{...field}
labelId='party-select-label'
label={t('party_name')}
MenuProps={selectMenuProps}
value={editMode ? initialValues && initialValues?.paketIds?.[0].items[0].cargoType : cargoType}
onChange={e => {
field.onChange(e);
handlePartyChange(e);
}}
value={field.value || initialValues?.partyId || ''} // Bu yerda partyId ishlatish kerak
disabled={isLoadingParties}
renderValue={selected => {
// Agar edit mode bo'lsa va initialValues mavjud bo'lsa
if (editMode && initialValues?.partyName && selected === initialValues?.partyId) {
return initialValues.partyName;
}
// Aks holda parties ro'yxatidan topish
const selectedParty = parties.find(p => p.id === selected);
return selectedParty ? selectedParty.name : '';
const value = e.target.value || undefined;
setCargoType(value as 'AVIA' | 'AUTO');
}}
displayEmpty
>
{isLoadingParties ? (
<MenuItem disabled>
<CircularProgress size={24} />
</MenuItem>
) : (
parties.map(party => (
<MenuItem key={party.id} value={party.id}>
{party.name}
</MenuItem>
))
)}
<MenuItem value='AVIA'>AVIA</MenuItem>
<MenuItem value='AUTO'>AUTO</MenuItem>
</Select>
)}
/>
{!!errors.partyId && <FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>}
</FormControl>
</FormControl>
)}
<FormControl fullWidth>
<InputLabel id='party-select-label'>{t('party_name')}</InputLabel>
<Controller
name='partyId'
control={control}
rules={{ required: requiredText }}
render={({ field }) => (
<Select
{...field}
labelId='party-select-label'
label={t('party_name')}
MenuProps={selectMenuProps}
onChange={e => {
field.onChange(e);
handlePartyChange(e);
}}
value={field.value || initialValues?.partyId || ''} // Bu yerda partyId ishlatish kerak
disabled={isLoadingParties}
renderValue={selected => {
// Agar edit mode bo'lsa va initialValues mavjud bo'lsa
if (editMode && initialValues?.partyName && selected === initialValues?.partyId) {
return initialValues.partyName;
}
// Aks holda parties ro'yxatidan topish
const selectedParty = parties.find(p => p.id === selected);
return selectedParty ? selectedParty.name : '';
}}
>
{isLoadingParties ? (
<MenuItem disabled>
<CircularProgress size={24} />
</MenuItem>
) : (
parties.map(party => (
<MenuItem key={party.id} value={party.id}>
{party.name}
</MenuItem>
))
)}
</Select>
)}
/>
{!!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) => {
setTimeout(() => {
setPage(newPage);
}, 100);
if (getBoxesQuery.data?.totalElements !== 0) {
setTimeout(() => {
setPage(newPage);
}, 100);
}
};
const resetFilter = () => {
@@ -152,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'),
@@ -369,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