diff --git a/package.json b/package.json index 72d956b..e66b076 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/[locale]/dashboard/forbidden/create/loading.tsx b/src/app/[locale]/dashboard/forbidden/create/loading.tsx new file mode 100644 index 0000000..3fbec00 --- /dev/null +++ b/src/app/[locale]/dashboard/forbidden/create/loading.tsx @@ -0,0 +1,9 @@ +import { CircularProgress, Stack } from '@mui/material'; + +export default function DashboardLoading() { + return ( + + + + ); +} diff --git a/src/app/[locale]/dashboard/forbidden/create/page.tsx b/src/app/[locale]/dashboard/forbidden/create/page.tsx new file mode 100644 index 0000000..cacd512 --- /dev/null +++ b/src/app/[locale]/dashboard/forbidden/create/page.tsx @@ -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 ; + } + + return ; +} diff --git a/src/app/[locale]/dashboard/forbidden/edit/[forbidden_id]/loading.tsx b/src/app/[locale]/dashboard/forbidden/edit/[forbidden_id]/loading.tsx new file mode 100644 index 0000000..3fbec00 --- /dev/null +++ b/src/app/[locale]/dashboard/forbidden/edit/[forbidden_id]/loading.tsx @@ -0,0 +1,9 @@ +import { CircularProgress, Stack } from '@mui/material'; + +export default function DashboardLoading() { + return ( + + + + ); +} diff --git a/src/app/[locale]/dashboard/forbidden/edit/[forbidden_id]/page.tsx b/src/app/[locale]/dashboard/forbidden/edit/[forbidden_id]/page.tsx new file mode 100644 index 0000000..f9ecf61 --- /dev/null +++ b/src/app/[locale]/dashboard/forbidden/edit/[forbidden_id]/page.tsx @@ -0,0 +1,5 @@ +import DashboardEditForbidden from '@/routes/private/forbidden-create/DashboardEditForbidden'; + +export default function Home() { + return ; +} diff --git a/src/app/[locale]/dashboard/forbidden/page.tsx b/src/app/[locale]/dashboard/forbidden/page.tsx new file mode 100644 index 0000000..40bcff9 --- /dev/null +++ b/src/app/[locale]/dashboard/forbidden/page.tsx @@ -0,0 +1,5 @@ +import DashboardForbiddenPage from '@/routes/private/forbidden'; + +export default function Home() { + return ; +} diff --git a/src/components/common/MyTable/MyTable.tsx b/src/components/common/MyTable/MyTable.tsx index adaf075..8129397 100644 --- a/src/components/common/MyTable/MyTable.tsx +++ b/src/components/common/MyTable/MyTable.tsx @@ -14,6 +14,7 @@ export interface ColumnData { getSxStyles?: (data: Data) => SxProps; 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 = { loading: boolean; onClickRow?: (data: Data) => void; color?: string; + disableSortingAndStatusFetch?: boolean; }; -const MyTable = (props: Props) => { +const MyTable = ({ disableSortingAndStatusFetch = true, ...props }: Props) => { const { columns, data, loading, onClickRow } = props; const isEmpty = !data?.length && !loading; - const [boxStatuses, setBoxStatuses] = React.useState>({}); // 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 = (props: Props) => { 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 = {}; @@ -131,7 +134,7 @@ const MyTable = (props: Props) => { }; fetchBoxStatuses(); - }, [data, loading]); + }, [data, loading, disableSortingAndStatusFetch]); return ( @@ -168,35 +171,30 @@ const MyTable = (props: Props) => { ) : ( sortedData.map((row: any, rowIndex) => { - const isCompleted = boxStatuses[row.id]; - + const isCompleted = columns.some(col => col.isComplated?.(row, rowIndex)) || boxStatuses[row.id]; return ( ), - roles: [UserRoleEnum.ADMIN, UserRoleEnum.CHINA_WORKER], + roles: [UserRoleEnum.ADMIN], }, + // { + // title: 'Taqiqlangan buyumlar', + // path: pageLinks.dashboard.forbidden.index, + // icon: ( + // + // + // + // + // + // + // + // + // + // + // ), + // roles: [UserRoleEnum.ADMIN], + // }, ]; diff --git a/src/data/box/box.model.ts b/src/data/box/box.model.ts index e9d59d4..7c15e98 100644 --- a/src/data/box/box.model.ts +++ b/src/data/box/box.model.ts @@ -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; diff --git a/src/data/box/box.requests.ts b/src/data/box/box.requests.ts index a5a4e67..b56aab4 100644 --- a/src/data/box/box.requests.ts +++ b/src/data/box/box.requests.ts @@ -12,6 +12,7 @@ export const box_requests = { cargoId?: string; partyId?: string | number; status?: BoxStatus; + cargoType?: 'AVIA' | 'AUTO'; }) { return request.get>>('/packets/list', { params }); }, diff --git a/src/data/item/item.mode.ts b/src/data/item/item.mode.ts index 6f5dd40..ed422a4 100644 --- a/src/data/item/item.mode.ts +++ b/src/data/item/item.mode.ts @@ -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; diff --git a/src/data/item/item.requests.ts b/src/data/item/item.requests.ts index 1323afc..80a2cdc 100644 --- a/src/data/item/item.requests.ts +++ b/src/data/item/item.requests.ts @@ -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'; diff --git a/src/data/party/party.model.ts b/src/data/party/party.model.ts index b709e3c..6e829f0 100644 --- a/src/data/party/party.model.ts +++ b/src/data/party/party.model.ts @@ -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'; diff --git a/src/data/party/party.requests.ts b/src/data/party/party.requests.ts index a03042d..4cdb560 100644 --- a/src/data/party/party.requests.ts +++ b/src/data/party/party.requests.ts @@ -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>>('/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('/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>( '/parties/update', - { name: body.name }, + { name: body.name, cargoType: body.cargoType }, { params: { partyId: body.id, diff --git a/src/data/real-box/real-box.model.ts b/src/data/real-box/real-box.model.ts index 774ae56..bc63709 100644 --- a/src/data/real-box/real-box.model.ts +++ b/src/data/real-box/real-box.model.ts @@ -26,6 +26,7 @@ export interface IRealBoxDetail { export interface RealCreateBoxBodyType { partyName: string; + cargoType: 'AUTO' | 'AVIA'; packetItemDtos: { packetId: number; itemDtos: number[] }[]; } diff --git a/src/data/real-box/real-box.requests.ts b/src/data/real-box/real-box.requests.ts index c39ff32..05b4829 100644 --- a/src/data/real-box/real-box.requests.ts +++ b/src/data/real-box/real-box.requests.ts @@ -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>>('/boxes/list', { params }); }, @@ -25,13 +26,13 @@ export const real_box_requests = { }); }, async find(params: { boxId?: number | string }) { - return request.get(`/boxes/find/${params.boxId}`,); + return request.get(`/boxes/find/${params.boxId}`); }, async delete(params: { boxId: number | string }) { return request.delete('/boxes/delete', { params }); }, async downloadExcel(params: { boxId: number | string }) { - return request.get(`/boxes/download/packets/${params.boxId}`, { responseType: "blob" }); + return request.get(`/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; }, -}; \ No newline at end of file +}; diff --git a/src/data/staff/staff.model.ts b/src/data/staff/staff.model.ts index fa43580..a4fd04d 100644 --- a/src/data/staff/staff.model.ts +++ b/src/data/staff/staff.model.ts @@ -5,4 +5,5 @@ export type CreateStaffBodyType = { role: string; phone: string; address: string; + cargoType: 'AUTO' | 'AVIA'; }; diff --git a/src/data/user/user.model.ts b/src/data/user/user.model.ts index 594c362..9ea43a4 100644 --- a/src/data/user/user.model.ts +++ b/src/data/user/user.model.ts @@ -13,4 +13,5 @@ export type User = { address?: string; role: UserRoleEnum; active: boolean; + cargoType: 'AUTO' | 'AVIA'; }; diff --git a/src/helpers/constants.ts b/src/helpers/constants.ts index b64302c..b536b77 100644 --- a/src/helpers/constants.ts +++ b/src/helpers/constants.ts @@ -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', }, diff --git a/src/routes/private/acceptance/DashboardAcceptancePage.tsx b/src/routes/private/acceptance/DashboardAcceptancePage.tsx index 3889cde..7781ba6 100644 --- a/src/routes/private/acceptance/DashboardAcceptancePage.tsx +++ b/src/routes/private/acceptance/DashboardAcceptancePage.tsx @@ -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(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 ...; + 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); + } }} > Chop etildi @@ -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')} /> + + { + const value = e.target.value || undefined; + setCargoType(value as 'AVIA' | 'AUTO'); + setPage(1); + }} + displayEmpty + > + AVIA + AUTO + + } size='small' onClick={resetFilter}> {t('reset_filter')} diff --git a/src/routes/private/boxes-create/DashboardCreateBox.tsx b/src/routes/private/boxes-create/DashboardCreateBox.tsx index 1ea8a68..e231e86 100644 --- a/src/routes/private/boxes-create/DashboardCreateBox.tsx +++ b/src/routes/private/boxes-create/DashboardCreateBox.tsx @@ -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(''); + 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) => { {editMode ? t('update_packet') : t('create_packet')} - + {user?.role === 'ADMIN' && ( + + { + const value = e.target.value || undefined; + setCargoType(value as 'AVIA' | 'AUTO'); + }} + displayEmpty + disabled={editMode} + > + AVIA + AUTO + + + )} @@ -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 ( { 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 } diff --git a/src/routes/private/boxes-create/DashboardEditBox.tsx b/src/routes/private/boxes-create/DashboardEditBox.tsx index 0fbc733..ab4e24f 100644 --- a/src/routes/private/boxes-create/DashboardEditBox.tsx +++ b/src/routes/private/boxes-create/DashboardEditBox.tsx @@ -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 ; diff --git a/src/routes/private/boxes-one/BoxesOne.tsx b/src/routes/private/boxes-one/BoxesOne.tsx index 58e4fe3..ab24f0e 100644 --- a/src/routes/private/boxes-one/BoxesOne.tsx +++ b/src/routes/private/boxes-one/BoxesOne.tsx @@ -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); diff --git a/src/routes/private/boxes/DashboardBoxesPage.tsx b/src/routes/private/boxes/DashboardBoxesPage.tsx index c37e23e..be14dc6 100644 --- a/src/routes/private/boxes/DashboardBoxesPage.tsx +++ b/src/routes/private/boxes/DashboardBoxesPage.tsx @@ -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(); 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([]); const [downloadIds, setDownloadIds] = useState([]); const [changeStatusIds, setChangeStatusIds] = useState([]); @@ -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 ...; + + const isCompleted = total.totalAmount === total.totalAccepted && total.totalAmount > 0; + return isCompleted; + }, renderCell: data => { const total = boxAmounts[data.id]; if (!total) return ...; @@ -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) => { } href={pageLinks.dashboard.boxes.create}> {t('create_packet')} - {t('product_inspection')} + {/* {t('product_inspection')} */} @@ -863,6 +879,22 @@ const DashboardBoxesPage = (props: Props) => { loadOptions={partyOptions} placeholder={t('filter_party_name')} /> + {user?.role === 'ADMIN' && ( + + { + const value = e.target.value || undefined; + setCargoType(value as 'AVIA' | 'AUTO'); + setPage(1); + }} + displayEmpty + > + AVIA + AUTO + + + )} } size='small' onClick={resetFilter}> {t('reset_filter')} diff --git a/src/routes/private/forbidden-create/DashboardCreateForbiddenPage.tsx b/src/routes/private/forbidden-create/DashboardCreateForbiddenPage.tsx new file mode 100644 index 0000000..4c9a811 --- /dev/null +++ b/src/routes/private/forbidden-create/DashboardCreateForbiddenPage.tsx @@ -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(''); + 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({ + 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(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 ( + + + + {editMode ? t('update_packet') : t('create_packet')} + + + + + + {t('party_name')} + + { + return ( + { + 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 && {errors.partyId?.message}} + + + + + {t('status')} + + { + return ( + 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 && {errors.box_type?.message}} + + + + + {t('cargo_id')} + + + {!!errors.net_weight?.message && ( + // @ts-expect-error + {errors.net_weight?.message} + )} + + + + {t('passport')} + + + { + return ( + { + 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')} + /> + ); + }} + /> + + + + + {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 ( + + + + + {t('track_id')} + + + ID + + ), + }} + fullWidth + placeholder={t('id')} + sx={{ + '.MuiInputBase-root': { + paddingLeft: 0, + }, + }} + {...register(`products_list.${index}.trekId`, { required: requiredText })} + /> + {!!get(errors, `products_list.${index}.trekId`) && ( + {requiredText} + )} + + + + {t('name')} + + { + translateAndUpdateRussianName(event.target.value, index); + }} + /> + {!!get(errors, `products_list.${index}.name`) && ( + {requiredText} + )} + + + + {'NAME_RU'} + + + {!!get(errors, `products_list.${index}.name`) && ( + {requiredText} + )} + + + + {t('quantity')} + + + {!!get(errors, `products_list.${index}.amount`) && ( + {requiredText} + )} + + + + {t('weight')} + + + {!!get(errors, `products_list.${index}.amount`) && ( + {requiredText} + )} + + {isAdmin && ( + + + + {t('weight')} + + + + + + + + {t('price')} + + + {!!get(errors, `products_list.${index}.price`) && ( + {requiredText} + )} + + + + {t('total_price')} + + + + + )} + + + removeProduct(index)} + > + + + + + + + ); + })} + + + } onClick={appendProduct}> + {t('add_more')} + + + + + + + + {editMode ? t('update') : t('create')} + + + + ); +}; + +export default DashboardCreateForbiddenPage; diff --git a/src/routes/private/forbidden-create/DashboardEditForbidden.tsx b/src/routes/private/forbidden-create/DashboardEditForbidden.tsx new file mode 100644 index 0000000..5a6a82a --- /dev/null +++ b/src/routes/private/forbidden-create/DashboardEditForbidden.tsx @@ -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 ; + } + + return ( + <> + + > + ); +}; + +export default DashboardEditForbidden; diff --git a/src/routes/private/forbidden/DashboardForbiddenPage.tsx b/src/routes/private/forbidden/DashboardForbiddenPage.tsx new file mode 100644 index 0000000..751e72e --- /dev/null +++ b/src/routes/private/forbidden/DashboardForbiddenPage.tsx @@ -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(undefined); + const [trackId, setTrackId] = useState(); + const [partyFilter, setPartyFilter] = useState<{ label: string; value: number } | undefined>(undefined); + const [boxFilter, setBoxFilter] = useState<{ label: string; value: number } | undefined>(undefined); + + const [deleteIds, setDeleteIds] = useState([]); + const [downloadIds, setDownloadIds] = useState([]); + const [changeStatusIds, setChangeStatusIds] = useState([]); + const [boxAmounts, setBoxAmounts] = useState>({}); + const [printStatuses, setPrintStatuses] = useState>({}); + const searchParams = useSearchParams(); + const boxId = searchParams.get('boxId'); + const { boxesId, setBoxId } = useBoxIdStore(); + + // Print uchun state + const [selectedBoxForPrint, setSelectedBoxForPrint] = useState(null); + const [selectedBoxDetails, setSelectedBoxDetails] = useState(null); + + const printRef = useRef(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, 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 = {}; + + 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[] = [ + { + label: t('No'), + width: 100, + renderCell(data, rowIndex) { + return (page - 1) * pageSize + rowIndex + 1; + }, + }, + { + dataKey: 'id', + label: "Qo'shish", + width: 120, + renderCell: data => { + return ( + navigation.push(pageLinks.dashboard.forbidden.edit(data.id))}> + + + ); + }, + }, + { + 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 ...; + + const isCompleted = total.totalAmount === total.totalAccepted && total.totalAmount > 0; + + return ( + + + {data.totalItems} | {total.totalAccepted} + + {isCompleted && } + + ); + }, + }, + { + 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 ( + , + label: t('edit'), + onClick: () => { + navigation.push(pageLinks.dashboard.forbidden.edit(data.id)); + }, + }, + { + icon: , + label: t('delete'), + onClick: () => { + onDelete(data.id); + }, + dontCloseOnClick: true, + loading: deleteIds.includes(data.id), + }, + ]} + /> + ); + }, + }, + ]; + + const [items, setItems] = useState(); + const [loaer, setLoading] = useState(false); + const { + register, + control, + handleSubmit, + watch, + setValue, + formState: { errors }, + } = useForm({ + 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 ( + + + } href={pageLinks.dashboard.forbidden.create}> + {t('create_packet')} + + + + + + {t('product_inspection')} + + + {t('enter_product')} + + { + setPartyFilter(newValue); + setPage(1); + }} + styles={selectDefaultStyles} + noOptionsMessage={() => t('not_found')} + loadingMessage={() => t('loading')} + defaultOptions={defaultPartyOptions!} + loadOptions={partyOptions} + placeholder={t('filter_party_name')} + /> + + { + setBoxFilter(newValue); + setPage(1); + }} + styles={selectDefaultStyles} + noOptionsMessage={() => t('enter_box_name_to_find')} + loadingMessage={() => t('loading')} + defaultOptions={defaultBoxOptions!} + loadOptions={boxOptions} + placeholder={t('filter_box_name')} + /> + , + }} + value={trackId} + onChange={e => setTrackId(e.target.value)} + placeholder={t('filter_item_name')} + /> + {trackId && trackId.length > 0 && ( + <> + {getListQuery.loading ? ( + {t('loading')}... + ) : getListQuery.data?.data && getListQuery.data?.data.length > 0 ? ( + getListQuery.data?.data.map(e => ( + + + + + {t('track_id')}: {e.trekId} + + Nomi: {e.name || e.nameRu} + Mahsulot soni: {e.amount} + Paket nomi: {e?.packetName} + + + { + if (values[e.trekId] !== '') { + updateItems(e, Number(values[e.trekId])); + } + }} + > + {t('confirmation')} + + + + )) + ) : ( + <> + {t('not_found') || 'Mahsulot topilmadi'} + + > + )} + > + )} + + + + + + + {t('packet')} + + + , + }} + placeholder={t('filter_packet_name')} + value={keyword} + onChange={e => { + setKeyword(e.target.value); + }} + /> + { + 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')} + /> + } size='small' onClick={resetFilter}> + {t('reset_filter')} + + + + + + + + + + + + ); +}; + +export default DashboardForbiddenPage; diff --git a/src/routes/private/forbidden/index.ts b/src/routes/private/forbidden/index.ts new file mode 100644 index 0000000..40fc86d --- /dev/null +++ b/src/routes/private/forbidden/index.ts @@ -0,0 +1 @@ +export { default } from './DashboardForbiddenPage'; diff --git a/src/routes/private/items/DashboardItemsPage.tsx b/src/routes/private/items/DashboardItemsPage.tsx index a0c2e67..c9db476 100644 --- a/src/routes/private/items/DashboardItemsPage.tsx +++ b/src/routes/private/items/DashboardItemsPage.tsx @@ -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(undefined); + const [cargoType, setCargoType] = useState<'AUTO' | 'AVIA'>(user?.role === 'ADMIN' ? 'AVIA' : user?.cargoType || 'AUTO'); const [deleteIds, setDeleteIds] = useState([]); @@ -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) => { /> - + {user?.role === 'ADMIN' && ( + + { + const value = e.target.value || undefined; + setCargoType(value as 'AVIA' | 'AUTO'); + setPage(1); + }} + displayEmpty + > + AVIA + AUTO + + + )} } size='small' onClick={resetFilter}> {t('reset_filter')} diff --git a/src/routes/private/items/components/EditItemModal.tsx b/src/routes/private/items/components/EditItemModal.tsx index 9f84886..725b25b 100644 --- a/src/routes/private/items/components/EditItemModal.tsx +++ b/src/routes/private/items/components/EditItemModal.tsx @@ -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); diff --git a/src/routes/private/parties-create/DashboardCreatePartyPage.tsx b/src/routes/private/parties-create/DashboardCreatePartyPage.tsx index 668fb71..b17787c 100644 --- a/src/routes/private/parties-create/DashboardCreatePartyPage.tsx +++ b/src/routes/private/parties-create/DashboardCreatePartyPage.tsx @@ -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 = 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')} - - - {t('party_name')} - - + + + + {t('party_name')} + + + + {user?.role === 'ADMIN' && ( + + { + const value = e.target.value || undefined; + setCargoType(value as 'AVIA' | 'AUTO'); + }} + displayEmpty + disabled={editMode} + > + AVIA + AUTO + + + )} diff --git a/src/routes/private/parties-create/DashboardEditPartyPage.tsx b/src/routes/private/parties-create/DashboardEditPartyPage.tsx index da74430..3fbd4c4 100644 --- a/src/routes/private/parties-create/DashboardEditPartyPage.tsx +++ b/src/routes/private/parties-create/DashboardEditPartyPage.tsx @@ -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, }; }, } diff --git a/src/routes/private/parties/DashboardPartiesPage.tsx b/src/routes/private/parties/DashboardPartiesPage.tsx index 6367c5a..bf7daba 100644 --- a/src/routes/private/parties/DashboardPartiesPage.tsx +++ b/src/routes/private/parties/DashboardPartiesPage.tsx @@ -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: , - label: t('download_all_items'), - onClick: () => { - onDownloadPartyItems(data.id); - }, - dontCloseOnClick: true, - loading: downloadItemIds.includes(data.id), - }, - ] + { + icon: , + label: t('download_all_items'), + onClick: () => { + onDownloadPartyItems(data.id); + }, + dontCloseOnClick: true, + loading: downloadItemIds.includes(data.id), + }, + ] : []), { icon: , @@ -328,16 +328,16 @@ const DashboardPartiesPage = (props: Props) => { }, ...(Boolean(data.partyStatus === 'ON_THE_WAY' || data.partyStatus === 'ARRIVED') ? [ - { - icon: , - label: t('download_excel'), - onClick: () => { - onDownloadExcel(data.id); - }, - loading: downloadIds.includes(data.id), - dontCloseOnClick: true, - }, - ] + { + icon: , + 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' && ( + + { + const value = e.target.value || undefined; + setCargoType(value as 'AVIA' | 'AUTO'); + setPage(1); + }} + displayEmpty + > + AVIA + AUTO + + + )} } size='small' onClick={resetFilter}> {t('reset_filter')} diff --git a/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx b/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx index 2fdcb29..67c2f68 100644 --- a/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx +++ b/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx @@ -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(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) => { {t('party_name')} - - {t('party_name')} - ( + + {user?.role === 'ADMIN' && ( + { - 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 ? ( - - - - ) : ( - parties.map(party => ( - - {party.name} - - )) - )} + AVIA + AUTO - )} - /> - {!!errors.partyId && {requiredText}} - + + )} + + {t('party_name')} + ( + { + 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 ? ( + + + + ) : ( + parties.map(party => ( + + {party.name} + + )) + )} + + )} + /> + {!!errors.partyId && {requiredText}} + + { const [pageSize] = useState(DEFAULT_PAGE_SIZE); const { value: keyword, onChange: handleKeyword, setValue: setKeyword } = useInput(''); const [boxStatusFilter, setBoxStatusFilter] = useState(undefined); - + const { user } = useAuth(); const [deleteIds, setDeleteIds] = useState([]); + const [cargoType, setCargoType] = useState<'AUTO' | 'AVIA'>('AVIA'); const [downloadIds, setDownloadIds] = useState([]); const [changeStatusIds, setChangeStatusIds] = useState([]); @@ -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[] = [ { label: t('No'), @@ -369,7 +373,22 @@ const DashboardRealBoxesPage = (props: Props) => { setKeyword(e.target.value); }} /> - + {user?.role === 'ADMIN' && ( + + { + const value = e.target.value || undefined; + setCargoType(value as 'AVIA' | 'AUTO'); + setPage(1); + }} + displayEmpty + > + AVIA + AUTO + + + )} } size='small' onClick={resetFilter}> {t('reset_filter')} diff --git a/src/routes/private/staffs/DashboardStaffsPage.tsx b/src/routes/private/staffs/DashboardStaffsPage.tsx index 2b73309..608b811 100644 --- a/src/routes/private/staffs/DashboardStaffsPage.tsx +++ b/src/routes/private/staffs/DashboardStaffsPage.tsx @@ -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'), diff --git a/src/routes/private/staffs/components/CreateStaffModal.tsx b/src/routes/private/staffs/components/CreateStaffModal.tsx index ec96fd1..02c3418 100644 --- a/src/routes/private/staffs/components/CreateStaffModal.tsx +++ b/src/routes/private/staffs/components/CreateStaffModal.tsx @@ -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 ( @@ -119,6 +131,26 @@ const CreateStaffModal = ({ onClose, open, onSuccess }: Props) => { }} /> + + Cargo Type + { + return ( + p.value === field.value)} + onChange={(newValue: any) => { + field.onChange(newValue.value); + }} + onBlur={field.onBlur} + name={field.name} + options={CARGO_TYPES} + /> + ); + }} + /> + {t('username')}