From 11faf4a2c9f3ec0f62180136b1319afefd73a9ca Mon Sep 17 00:00:00 2001 From: Samandar Turg'unboev Date: Tue, 24 Jun 2025 09:34:41 +0500 Subject: [PATCH 1/2] accepted items added --- messages/cn.json | 7 +- messages/en.json | 7 +- messages/ru.json | 6 +- messages/uz.json | 5 + package.json | 148 ++++---- .../dashboard/packets/[box_id]/loading.tsx | 9 + .../dashboard/packets/[box_id]/page.tsx | 5 + src/components/common/MyTable/MyTable.tsx | 79 ++-- src/data/box/box.model.ts | 1 + src/data/item/item.mode.ts | 9 +- src/data/item/item.requests.ts | 2 +- src/helpers/constants.ts | 1 + .../boxes-create/DashboardCreateBox.tsx | 98 +++-- .../private/boxes-create/DashboardEditBox.tsx | 3 +- src/routes/private/boxes-one/BoxesOne.tsx | 322 +++++++++++++++++ src/routes/private/boxes-one/index.ts | 1 + .../private/boxes/DashboardBoxesPage.tsx | 221 +++++++++++- .../private/clients/DashboardClientsPage.tsx | 17 +- .../DashboardCreateRealBox.tsx | 341 +++++++++--------- 19 files changed, 957 insertions(+), 325 deletions(-) create mode 100644 src/app/[locale]/dashboard/packets/[box_id]/loading.tsx create mode 100644 src/app/[locale]/dashboard/packets/[box_id]/page.tsx create mode 100644 src/routes/private/boxes-one/BoxesOne.tsx create mode 100644 src/routes/private/boxes-one/index.ts diff --git a/messages/cn.json b/messages/cn.json index bc9ec53..1dfba28 100644 --- a/messages/cn.json +++ b/messages/cn.json @@ -212,5 +212,10 @@ "qr_code": "二维码", "created_at": "添加日期", "download_all_items_exel": "将所有产品上传至Excel", - "select_all": "全选," + "select_all": "全选,", + "product_inspection": "产品检验", + "enter_product": "请输入产品的追溯ID", + "confirmation": "确认", + "view_packet": "查看包裹数据", + "accepted_number": "已接收" } diff --git a/messages/en.json b/messages/en.json index ad135dc..875ce01 100644 --- a/messages/en.json +++ b/messages/en.json @@ -212,5 +212,10 @@ "download_all_items_exel": "Upload all products in Excel", "select_all": "Select all", "qr_code": "QR Code", - "created_at": "Date of joining" + "created_at": "Date of joining", + "product_inspection": "Product Inspection", + "enter_product": "Enter your product tracking ID", + "confirmation": "Confirm", + "view_packet": "View package data", + "accepted_number": "Accepted" } diff --git a/messages/ru.json b/messages/ru.json index 06e4dd9..9deafb9 100644 --- a/messages/ru.json +++ b/messages/ru.json @@ -224,7 +224,11 @@ "party_weight": "Вес партии", "download_all_items_exel": "Загрузить все товары в Excel", "select_all": "Выделить все", - + "product_inspection": "Проверка товаров", + "enter_product": "Enter your product tracking ID", + "confirmation": "Подтверждение", + "view_packet": "Просмотреть данные посылки", + "accepted_number": "Принято", "qr_code": "QR код", "created_at": "Дата добавления" } diff --git a/messages/uz.json b/messages/uz.json index 49be7bd..1f1ab7c 100644 --- a/messages/uz.json +++ b/messages/uz.json @@ -117,6 +117,8 @@ "create_packet": "Paket yaratish", "update_box": "Qutni yangilash", "update_package": "Paketni yangilash", + "view_packet": "Paketni ma'lumotlarini ko'rish", + "accepted_number": "Qabul qilingan", "update_item": "Mahsulotni yangilash", "no": "Yo'q", "id": "ID", @@ -223,6 +225,9 @@ "update_packet": "Paketni Tahrirlash", "party_weight": "Partiya og'irligi", "select_all": "Hammasini belgilash", + "product_inspection": "Mahsulotlarni tekshirish", + "enter_product": "Mahsulotning Trassirovka IDni kiriting", + "confirmation": "Tasdiqlash", "qr_code": "QR kod", "created_at": "Qo‘shilgan sana" diff --git a/package.json b/package.json index 230c137..7441c04 100644 --- a/package.json +++ b/package.json @@ -1,75 +1,75 @@ { - "name": "c-post", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "cross-env NEXT_PUBLIC_API_URL=https://cpost.felixits.uz next dev --port=3080", - "build": "cross-env NEXT_PUBLIC_API_URL=https://api.cpost-express.uz next build", - "build:dev": "cross-env NEXT_PUBLIC_API_URL=https://cpost.felixits.uz next build", - "build:prod": "cross-env NEXT_PUBLIC_API_URL=https://api.cpost-express.uz next build", - "start": "next start", - "lint": "next lint", - "test": "vitest", - "check-types": "tsc --noemit", - "format-code": "prettier --write .", - "prepare": "husky install" - }, - "dependencies": { - "@emotion/cache": "^11.11.0", - "@emotion/react": "^11.11.3", - "@emotion/styled": "^11.11.0", - "@mui/icons-material": "^5.15.3", - "@mui/material": "^5.15.3", - "@mui/material-nextjs": "^5.15.3", - "@tanstack/react-query": "^4.35.0", - "@tanstack/react-query-next-experimental": "^5.17.9", - "aos": "^2.3.4", - "axios": "^1.6.5", - "cookies-next": "^4.1.0", - "i18next": "^23.7.16", - "lodash.clonedeep": "^4.5.0", - "lodash.debounce": "^4.0.8", - "lodash.get": "^4.4.2", - "lodash.omit": "^4.5.0", - "next": "14.0.4", - "next-i18next": "^15.2.0", - "next-intl": "^3.4.2", - "nextjs-progressbar": "^0.0.16", - "react": "^18", - "react-dom": "^18", - "react-hook-form": "^7.49.3", - "react-hot-toast": "^2.4.1", - "react-i18next": "^14.0.0", - "react-select": "^5.8.0", - "simplebar-react": "^3.2.4", - "swiper": "^11.0.5", - "use-dehydrated-state": "^0.1.0", - "zustand": "^4.4.7" - }, - "devDependencies": { - "@tanstack/react-query-devtools": "^4.35.3", - "@types/aos": "^3.0.7", - "@types/i18next": "^13.0.0", - "@types/lodash.clonedeep": "^4.5.9", - "@types/lodash.debounce": "^4.0.9", - "@types/lodash.get": "^4.4.9", - "@types/lodash.omit": "^4.5.9", - "@types/node": "^20", - "@types/parse-json": "^4.0.2", - "@types/react": "^18", - "@types/react-dom": "^18", - "@types/react-i18next": "^8.1.0", - "@types/react-select": "^5.0.1", - "@vitejs/plugin-react": "^4.2.1", - "cross-env": "^7.0.3", - "eslint": "^8", - "eslint-config-next": "14.0.4", - "husky": "^8.0.0", - "jsdom": "^23.2.0", - "lint-staged": "^15.2.0", - "prettier": "^3.1.1", - "typescript": "^5", - "vitest": "^1.1.3" - }, - "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" -} \ No newline at end of file + "name": "c-post", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "cross-env NEXT_PUBLIC_API_URL=https://cpcargo.felixits.uz next dev --port=3080", + "build": "cross-env NEXT_PUBLIC_API_URL=https://api.cpost-express.uz next build", + "build:dev": "cross-env NEXT_PUBLIC_API_URL=https://cpcargo.felixits.uz next build", + "build:prod": "cross-env NEXT_PUBLIC_API_URL=https://api.cpost-express.uz next build", + "start": "next start", + "lint": "next lint", + "test": "vitest", + "check-types": "tsc --noemit", + "format-code": "prettier --write .", + "prepare": "husky install" + }, + "dependencies": { + "@emotion/cache": "^11.11.0", + "@emotion/react": "^11.11.3", + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.15.3", + "@mui/material": "^5.15.3", + "@mui/material-nextjs": "^5.15.3", + "@tanstack/react-query": "^4.35.0", + "@tanstack/react-query-next-experimental": "^5.17.9", + "aos": "^2.3.4", + "axios": "^1.6.5", + "cookies-next": "^4.1.0", + "i18next": "^23.7.16", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.get": "^4.4.2", + "lodash.omit": "^4.5.0", + "next": "14.0.4", + "next-i18next": "^15.2.0", + "next-intl": "^3.4.2", + "nextjs-progressbar": "^0.0.16", + "react": "^18", + "react-dom": "^18", + "react-hook-form": "^7.49.3", + "react-hot-toast": "^2.4.1", + "react-i18next": "^14.0.0", + "react-select": "^5.8.0", + "simplebar-react": "^3.2.4", + "swiper": "^11.0.5", + "use-dehydrated-state": "^0.1.0", + "zustand": "^4.4.7" + }, + "devDependencies": { + "@tanstack/react-query-devtools": "^4.35.3", + "@types/aos": "^3.0.7", + "@types/i18next": "^13.0.0", + "@types/lodash.clonedeep": "^4.5.9", + "@types/lodash.debounce": "^4.0.9", + "@types/lodash.get": "^4.4.9", + "@types/lodash.omit": "^4.5.9", + "@types/node": "^20", + "@types/parse-json": "^4.0.2", + "@types/react": "^18", + "@types/react-dom": "^18", + "@types/react-i18next": "^8.1.0", + "@types/react-select": "^5.0.1", + "@vitejs/plugin-react": "^4.2.1", + "cross-env": "^7.0.3", + "eslint": "^8", + "eslint-config-next": "14.0.4", + "husky": "^8.0.0", + "jsdom": "^23.2.0", + "lint-staged": "^15.2.0", + "prettier": "^3.1.1", + "typescript": "^5", + "vitest": "^1.1.3" + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" +} diff --git a/src/app/[locale]/dashboard/packets/[box_id]/loading.tsx b/src/app/[locale]/dashboard/packets/[box_id]/loading.tsx new file mode 100644 index 0000000..3fbec00 --- /dev/null +++ b/src/app/[locale]/dashboard/packets/[box_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/packets/[box_id]/page.tsx b/src/app/[locale]/dashboard/packets/[box_id]/page.tsx new file mode 100644 index 0000000..4957124 --- /dev/null +++ b/src/app/[locale]/dashboard/packets/[box_id]/page.tsx @@ -0,0 +1,5 @@ +import DashboardBoxesOnePage from '@/routes/private/boxes-one'; + +export default function Home() { + return ; +} diff --git a/src/components/common/MyTable/MyTable.tsx b/src/components/common/MyTable/MyTable.tsx index 749c0ea..a962314 100644 --- a/src/components/common/MyTable/MyTable.tsx +++ b/src/components/common/MyTable/MyTable.tsx @@ -1,23 +1,8 @@ import Loader from '@/components/common/Loader'; import { Scrollbar } from '@/components/common/Scrollbar'; -import BaseButton from '@/components/ui-kit/BaseButton'; -import BaseIconButton from '@/components/ui-kit/BaseIconButton'; -import { FilterList, Search } from '@mui/icons-material'; -import { - Box, - IconButton, - Stack, - SxProps, - Table, - TableBody, - TableHead, - TableRow, - Theme, - Tooltip, - Typography, - styled, - TableCell, -} from '@mui/material'; +import { box_requests } from '@/data/box/box.requests'; +import useRequest from '@/hooks/useRequest'; +import { Box, styled, SxProps, Table, TableBody, TableCell, TableHead, TableRow, Theme } from '@mui/material'; import React from 'react'; export interface ColumnData { @@ -75,10 +60,11 @@ type Props = { data: Data[]; loading: boolean; onClickRow?: (data: Data) => void; + color?: string; }; const MyTable = (props: Props) => { - const { columns, data, loading, onClickRow } = props; + const { columns, data, loading, onClickRow, color } = props; const isEmpty = !data?.length && !loading; @@ -113,11 +99,63 @@ const MyTable = (props: Props) => { ) : ( - data.map((row, rowIndex) => { + data.map((row: any, rowIndex) => { + let status = undefined; + const getOneBox = useRequest( + () => { + return box_requests.find({ packetId: row.id }); + }, + { + selectData(data) { + const boxData = data.data.data; + + return { + products_list: [ + ...boxData.items.map(item => { + let name = item.name; + let nameRu = item.nameRu; + + return { + id: item.id, + price: item.price, + + cargoId: item.cargoId, + trekId: item.trekId, + name: name, + acceptedNumber: item.acceptedNumber, + nameRu: nameRu, + amount: +item.amount, + weight: +item.weight, + }; + }), + ], + }; + }, + } + ); + { + (() => { + const total = getOneBox.data?.products_list.reduce( + (acc, product) => { + console.log(product, 'totalAccepted'); + acc.totalAmount += +product.amount || 0; + acc.totalAccepted += +product.acceptedNumber || 0; + return acc; + }, + { totalAmount: 0, totalAccepted: 0 } + ); + + status = total?.totalAmount === total?.totalAccepted; + })(); + } + + console.log(row, 'clent'); + return ( (props: Props) => { width: column.width, }} > - {/* @ts-expect-error */} {column.renderCell ? column.renderCell(row, rowIndex) : row[column.dataKey]} ))} diff --git a/src/data/box/box.model.ts b/src/data/box/box.model.ts index da70a25..22e30d1 100644 --- a/src/data/box/box.model.ts +++ b/src/data/box/box.model.ts @@ -52,6 +52,7 @@ export interface IBoxDetail { nameRu: string; amount: number; weight: number; + acceptedNumber: number; price: number; totalPrice: number; hasImage: boolean; diff --git a/src/data/item/item.mode.ts b/src/data/item/item.mode.ts index badba5c..3492ed3 100644 --- a/src/data/item/item.mode.ts +++ b/src/data/item/item.mode.ts @@ -11,9 +11,10 @@ export type Product = { amount: number; weight: number; price?: number; + packetName: string; totalPrice?: number; status: BoxStatus; - + acceptedNumber: number; hasImage: boolean; }; @@ -22,11 +23,11 @@ export type CreateProductBodyType = { }; export type UpdateProductBodyType = { - itemId: number; - - cargoId: string; + itemId: string | number; trekId: string; name: string; + nameRu: string; amount: number; weight: number; + acceptedNumber: number; }; diff --git a/src/data/item/item.requests.ts b/src/data/item/item.requests.ts index 664280e..1323afc 100644 --- a/src/data/item/item.requests.ts +++ b/src/data/item/item.requests.ts @@ -8,7 +8,7 @@ export const item_requests = { packetId?: number | string; partyId?: number | string; name?: string; - trekId?: string; + trekId?: string | number; page?: number; sort?: string; status?: BoxStatus; diff --git a/src/helpers/constants.ts b/src/helpers/constants.ts index 3853f3e..51579d3 100644 --- a/src/helpers/constants.ts +++ b/src/helpers/constants.ts @@ -15,6 +15,7 @@ export const pageLinks = { }, boxes: { index: '/dashboard/packets', + detail: (slug: string | number) => '/dashboard/packets/' + slug, create: '/dashboard/packets/create', edit: (slug: string | number) => '/dashboard/packets/edit/' + slug, }, diff --git a/src/routes/private/boxes-create/DashboardCreateBox.tsx b/src/routes/private/boxes-create/DashboardCreateBox.tsx index 1764912..ce6dda3 100644 --- a/src/routes/private/boxes-create/DashboardCreateBox.tsx +++ b/src/routes/private/boxes-create/DashboardCreateBox.tsx @@ -1,34 +1,31 @@ 'use client'; 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 { notifyError, notifyUnknownError } from '@/services/notification'; -import { Box, Divider, FormHelperText, Grid, Stack, Typography, styled } from '@mui/material'; -import { useParams, useRouter, useSearchParams } from 'next/navigation'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { useLocale } from 'use-intl'; -import { Controller, useFieldArray, useForm } from 'react-hook-form'; import BaseIconButton from '@/components/ui-kit/BaseIconButton'; -import { AddCircleRounded, Close } from '@mui/icons-material'; -import { box_requests } from '@/data/box/box.requests'; -import useRequest from '@/hooks/useRequest'; -import { useMyTranslation } from '@/hooks/useMyTranslation'; -import { BoxStatus, CreateBoxBodyType, UpdateBoxBodyType } from '@/data/box/box.model'; +import BaseInput from '@/components/ui-kit/BaseInput'; import BaseReactSelect, { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect'; -import { customer_requests } from '@/data/customers/customer.requests'; import { useAuthContext } from '@/context/auth-context'; -import { useMyNavigation } from '@/hooks/useMyNavigation'; -import AsyncSelect from 'react-select/async'; -import { Party } from '@/data/party/party.model'; -import cloneDeep from 'lodash.clonedeep'; -import { item_requests } from '@/data/item/item.requests'; -import get from 'lodash.get'; -import Loader from '@/components/common/Loader'; +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 { @@ -123,19 +120,19 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => { ...(editMode ? {} : { - products_list: [ - { - id: '', - cargoId: '', - trekId: '', - name: '', - nameRu: '', - amount: '', - weight: '', - price: '', - }, - ], - }), + products_list: [ + { + id: '', + cargoId: '', + trekId: '', + name: '', + nameRu: '', + amount: '', + weight: '', + price: '', + }, + ], + }), ...initialValues, }, }); @@ -160,8 +157,8 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => { label: initialValues?.passportName, }, ]; - const n = "123ds" - n.toUpperCase() + const n = '123ds'; + n.toUpperCase(); const { data: passportOptions } = useRequest(() => passport_requests.getAll({ cargoId: cargoId?.toUpperCase() }), { enabled: !!cargoId, selectData: data => { @@ -185,7 +182,7 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => { placeholderData: [], // Kerak emas, chunki server PageAble qaytarmayapti onSuccess(data) { if (data?.data.data?.[0]?.id) { - setValue("passportId", initialValues?.passportId) + setValue('passportId', initialValues?.passportId); setValue('passport_id', data.data.data[0].id); setSelectedPassport(data.data.data[0]); // Birinchi elementni tanlash } @@ -251,7 +248,6 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => { }; const onSubmit = handleSubmit(async values => { - try { setLoading(true); @@ -377,11 +373,11 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => { label: string; value: BoxStatus; }[] = [ - { - label: t('READY_TO_INVOICE'), - value: 'READY_TO_INVOICE', - }, - ]; + { + label: t('READY_TO_INVOICE'), + value: 'READY_TO_INVOICE', + }, + ]; if (isAdmin) { p.push({ @@ -426,15 +422,15 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => { defaultValue={ editMode ? { - value: initialValues.partyId, - label: initialValues.partyName, - } + value: initialValues.partyId, + label: initialValues.partyName, + } : partiesData?.length - ? { + ? { value: partiesData[0].value, label: partiesData[0].label, } - : null + : null } styles={selectDefaultStyles} noOptionsMessage={() => t('not_found')} @@ -552,7 +548,7 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => { if (!Number.isNaN(p)) { totalPrice = p; } - } catch (error) { } + } catch (error) {} return ( @@ -707,7 +703,7 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => { value={totalPrice} mainBorderColor='#D8D8D8' placeholder={t('total_price')} - // {...register(`products_list.${index}.totalPrice`, { required: requiredText })} + // {...register(`products_list.${index}.totalPrice`, { required: requiredText })} /> diff --git a/src/routes/private/boxes-create/DashboardEditBox.tsx b/src/routes/private/boxes-create/DashboardEditBox.tsx index de89b8b..c3269f7 100644 --- a/src/routes/private/boxes-create/DashboardEditBox.tsx +++ b/src/routes/private/boxes-create/DashboardEditBox.tsx @@ -5,7 +5,6 @@ import { box_requests } from '@/data/box/box.requests'; import useRequest from '@/hooks/useRequest'; import DashboardCreateBoxPage from '@/routes/private/boxes-create/DashboardCreateBox'; import { useParams } from 'next/navigation'; -import React from 'react'; type Props = {}; @@ -71,7 +70,7 @@ const DashboardEditBoxPage = (props: Props) => { } ); - console.log(getOneBox, "pkets"); + console.log(getOneBox, 'pkets'); if (getOneBox.loading || !getOneBox.data) { return ; diff --git a/src/routes/private/boxes-one/BoxesOne.tsx b/src/routes/private/boxes-one/BoxesOne.tsx new file mode 100644 index 0000000..642de94 --- /dev/null +++ b/src/routes/private/boxes-one/BoxesOne.tsx @@ -0,0 +1,322 @@ +'use client'; + +import Loader from '@/components/common/Loader'; +import BaseInput from '@/components/ui-kit/BaseInput'; +import { useAuthContext } from '@/context/auth-context'; +import { BoxStatus } from '@/data/box/box.model'; +import { box_requests } from '@/data/box/box.requests'; +import { useMyTranslation } from '@/hooks/useMyTranslation'; +import useRequest from '@/hooks/useRequest'; +import { Box, Divider, Grid, Stack, Typography, styled } from '@mui/material'; +import { useParams } from 'next/navigation'; +import { useMemo } from 'react'; + +const StyledViewBox = styled(Box)` + .item-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + } + + .item-row-field { + flex: 1; + } + + & > * { + flex: 1 1 auto; + } +`; + +const DashboardBoxesOnePage = () => { + const params = useParams(); + const box_id = params.box_id as string; + const { isAdmin: isAdminUser } = useAuthContext(); + const t = useMyTranslation(); + + const { data: boxData, loading } = useRequest(() => 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, + passportId: boxData.client?.passportId, + client_id: boxData.packet?.cargoId, + clientName: boxData.client?.passportName, + products_list: boxData.items.map(item => ({ + 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, + })), + }; + }, + }); + + const boxStatuses = useMemo(() => { + const statuses: { label: string; value: BoxStatus }[] = [ + { + label: t('READY_TO_INVOICE'), + value: 'READY_TO_INVOICE', + }, + ]; + + if (isAdminUser) { + statuses.push({ + label: t('READY'), + value: 'READY', + }); + } + + return statuses; + }, [isAdminUser, t]); + + if (loading || !boxData) { + return ; + } + + return ( + + + + {t('view_packet')} + + + + + + {t('party_name')} + + + {boxData.partyName || '-'} + + + + + + {t('cargo_id')} + + + {boxData.client_id || '-'} + + + + + + {t('passport')} + + + {boxData.passportName || '-'} + + + + + + {boxData.products_list.map((product, 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')} + disabled + defaultValue={product.trekId} + sx={{ + '.MuiInputBase-root': { + paddingLeft: 0, + }, + }} + /> + + + + {t('name')} + + + + + + {'NAME_RU'} + + + + + + {t('quantity')} + + + + + + {t('accepted_number')} + + + + <> + + + {t('weight')} + + + + + + + + {t('price')} + + + + + + {t('total_price')} + + + + + + + + ); + })} + + + + + + ); +}; + +export default DashboardBoxesOnePage; diff --git a/src/routes/private/boxes-one/index.ts b/src/routes/private/boxes-one/index.ts new file mode 100644 index 0000000..ebd959a --- /dev/null +++ b/src/routes/private/boxes-one/index.ts @@ -0,0 +1 @@ +export { default } from './BoxesOne'; diff --git a/src/routes/private/boxes/DashboardBoxesPage.tsx b/src/routes/private/boxes/DashboardBoxesPage.tsx index b2856f4..6286eed 100644 --- a/src/routes/private/boxes/DashboardBoxesPage.tsx +++ b/src/routes/private/boxes/DashboardBoxesPage.tsx @@ -9,6 +9,8 @@ import BasePagination from '@/components/ui-kit/BasePagination'; import { useAuthContext } from '@/context/auth-context'; import { BoxStatus, BoxStatusList, IBox } from '@/data/box/box.model'; import { box_requests } from '@/data/box/box.requests'; +import { Product, UpdateProductBodyType } from '@/data/item/item.mode'; +import { item_requests } from '@/data/item/item.requests'; import { DEFAULT_PAGE_SIZE, pageLinks } from '@/helpers/constants'; import useInput from '@/hooks/useInput'; import { useMyNavigation } from '@/hooks/useMyNavigation'; @@ -17,13 +19,32 @@ import useRequest from '@/hooks/useRequest'; import { file_service } from '@/services/file-service'; import { notifyUnknownError } from '@/services/notification'; import { getStatusColor } from '@/theme/getStatusBoxStyles'; -import { Add, Circle, Delete, Download, Edit, FilterList, FilterListOff, Search } from '@mui/icons-material'; -import { Box, Button, Stack, Typography } from '@mui/material'; +import { Add, Circle, Delete, Download, Edit, FilterList, FilterListOff, RemoveRedEye, Search } from '@mui/icons-material'; +import { Box, Button, Card, CardContent, Modal, Stack, TextField, Typography } from '@mui/material'; import { useEffect, useMemo, useState } from 'react'; +import { useForm } from 'react-hook-form'; 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 DashboardBoxesPage = (props: Props) => { + const [open, setOpen] = useState(false); + const handleOpen = () => setOpen(true); + const handleClose = () => setOpen(false); const t = useMyTranslation(); const navigation = useMyNavigation(); const { isAdmin } = useAuthContext(); @@ -31,6 +52,7 @@ const DashboardBoxesPage = (props: Props) => { const [pageSize] = useState(DEFAULT_PAGE_SIZE); const { value: keyword, onChange: handleKeyword, setValue: setKeyword } = useInput(''); const [boxStatusFilter, setBoxStatusFilter] = useState(undefined); + const [trackId, setTrackId] = useState(); const [deleteIds, setDeleteIds] = useState([]); const [downloadIds, setDownloadIds] = useState([]); @@ -61,6 +83,31 @@ const DashboardBoxesPage = (props: Props) => { } ); + const getListQuery = useRequest( + () => + item_requests.getAll({ + page: page, + trekId: trackId, + }), + { + dependencies: [page, trackId], + 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]: '' })); + } + }; + const { data: list, totalElements, @@ -185,6 +232,62 @@ const DashboardBoxesPage = (props: Props) => { dataKey: 'totalItems', label: t('count_of_items'), width: 120, + renderCell: data => { + const getOneBox = useRequest( + () => { + return box_requests.find({ packetId: data.id }); + }, + { + selectData(data) { + const boxData = data.data.data; + + return { + products_list: [ + ...boxData.items.map(item => { + let name = item.name; + let nameRu = item.nameRu; + + return { + id: item.id, + price: item.price, + + cargoId: item.cargoId, + trekId: item.trekId, + name: name, + acceptedNumber: item.acceptedNumber, + nameRu: nameRu, + amount: +item.amount, + weight: +item.weight, + }; + }), + ], + }; + }, + } + ); + + return ( +
+ {(() => { + const total = getOneBox.data?.products_list.reduce( + (acc, product) => { + console.log(product, 'totalAccepted'); + acc.totalAmount += +product.amount || 0; + acc.totalAccepted += +product.acceptedNumber || 0; + return acc; + }, + { totalAmount: 0, totalAccepted: 0 } + ); + + return ( + + {total?.totalAmount} | {total?.totalAccepted} + + ); + })()} +
+ ); + }, }, { dataKey: 'totalNetWeight', @@ -272,6 +375,13 @@ const DashboardBoxesPage = (props: Props) => { return ( , + label: t('view_packet'), + onClick: () => { + navigation.push(pageLinks.dashboard.boxes.detail(data.id)); + }, + }, { icon: , label: t('edit'), @@ -307,6 +417,53 @@ const DashboardBoxesPage = (props: Props) => { }, }, ]; + 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, + amount: item.amount, + name: item.name, + nameRu: item.nameRu, + trekId: item.trekId, + weight: item.weight, + }; + + await item_requests.update(updateBody); + + // Ma'lumotni yangilab olamiz + getListQuery.refetch(); + getBoxesQuery.refetch(); + + setValues(prev => ({ ...prev, [item.trekId]: '' })); + } catch (error) { + notifyUnknownError(error); + } finally { + setLoading(false); + } + }; return ( @@ -314,6 +471,66 @@ const DashboardBoxesPage = (props: Props) => { } href={pageLinks.dashboard.boxes.create}> {t('create_packet')} + + + + + {t('product_inspection')} + + + {t('enter_product')} + + setTrackId(e.target.value)} + /> + {trackId && trackId.length > 0 && ( + <> + {getListQuery.loading ? ( + {t('loading')}... // yoki + ) : 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} + + + handleAmountChange(change, e.amount, e.trekId)} + inputProps={{ min: 1, max: e.amount }} + /> + + + )) + ) : ( + {t('no_products_found') || 'Mahsulot topilmadi'} + )} + + )} + + { const [page, setPage] = useState(1); const [pageSize] = useState(DEFAULT_PAGE_SIZE); const { value: keyword, onChange: handleKeyword, setValue: setKeyword } = useInput(''); + const { value: name, onChange: handleName, setValue: setName } = useInput(''); const { value: aviaCargoIdValue, onChange: handleAviaCargoIdValue, setValue: setAviaCargoIdValue } = useInput(''); const { value: autoCargoIdValue, onChange: handleAutoCargoIdValue, setValue: setAutoCargoIdValue } = useInput(''); @@ -42,7 +43,8 @@ const DashboardClientsPage = (props: Props) => { () => customer_requests.getAll({ page: page, - clientName: keyword, + clientName: name, + autoCargoId: keyword, }), { selectData(data) { @@ -112,6 +114,7 @@ const DashboardClientsPage = (props: Props) => { setPage(1); setKeyword(''); setAviaCargoIdValue(''); + setName(''); setAutoCargoIdValue(''); }; @@ -136,7 +139,7 @@ const DashboardClientsPage = (props: Props) => { getClientsQuery.refetch(); }, 350); return () => clearTimeout(timeoutId); - }, [keyword, aviaCargoIdValue, autoCargoIdValue]); + }, [keyword, aviaCargoIdValue, autoCargoIdValue, name]); const columns: ColumnData[] = [ { @@ -332,13 +335,21 @@ const DashboardClientsPage = (props: Props) => { {/* onChange={handleAutoCargoIdValue}*/} {/* placeholder={t('auto_cargo_id')}*/} {/*/>*/} + , + }} + value={name} + onChange={handleName} + placeholder={t('name')} + /> , }} value={keyword} onChange={handleKeyword} - placeholder={'Kargo ID'} + placeholder={t('id')} /> } size='small' onClick={resetFilter}> diff --git a/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx b/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx index 4ead500..5a4a5c8 100644 --- a/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx +++ b/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx @@ -34,7 +34,7 @@ import { import { useQuery } from '@tanstack/react-query'; import get from 'lodash.get'; import { useSearchParams } from 'next/navigation'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Controller, useFieldArray, useForm } from 'react-hook-form'; const StyledCreateBox = styled(Box)` @@ -67,9 +67,26 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { const t = useMyTranslation(); const params = useSearchParams(); const { push } = useMyNavigation(); - const [partyId, setPartyId] = useState(''); + const [partyId, setPartyId] = useState(initialValues?.partyId || ''); const [loading, setLoading] = useState(false); + const selectMenuProps = useMemo( + () => ({ + PaperProps: { style: { maxHeight: 280 } }, + autoFocus: false, + disableAutoFocus: true, + disableEnforceFocus: true, + disableRestoreFocus: true, + disableScrollLock: true, + }), + [] + ); + + // Barcha box/packetlarni barcha sahifadan yuklash uchun state + const [allPackets, setAllPackets] = useState([]); + // Barcha mahsulotlarni barcha sahifadan yuklash uchun map + const [allItemsMap, setAllItemsMap] = useState<{ [packetId: number]: any[] }>({}); + const { control, handleSubmit, @@ -94,6 +111,65 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { }, }); + // Paketlar va mahsulotlarni yuklash + const [isLoadingPackets, setIsLoadingPackets] = useState(false); + useEffect(() => { + const fetchAllPackets = async () => { + if (!partyId) { + setAllPackets([]); + return; + } + setIsLoadingPackets(true); + let packets: any[] = []; + let totalPages = 1; + try { + const firstRes = await box_requests.getAll({ partyId, page: 1 }); + const firstData = firstRes?.data?.data; + packets = firstData?.data || []; + totalPages = firstData?.totalPages || 1; + + if (totalPages > 1) { + const promises = []; + for (let page = 2; page <= totalPages; page++) { + promises.push(box_requests.getAll({ partyId, page })); + } + const results = await Promise.all(promises); + results.forEach(res => { + const data = res?.data?.data; + packets = [...packets, ...(data?.data || [])]; + }); + } + } catch (e) {} + setAllPackets(packets); + setIsLoadingPackets(false); + }; + fetchAllPackets(); + }, [partyId]); + + const fetchAllItemsForPacket = async (packetId: number) => { + let items: any[] = []; + let totalPages = 1; + try { + const firstRes = await item_requests.getAll({ packetId, page: 1 }); + const firstData = firstRes?.data?.data; + items = firstData?.data || []; + totalPages = firstData?.totalPages || 1; + if (totalPages > 1) { + const promises = []; + for (let page = 2; page <= totalPages; page++) { + promises.push(item_requests.getAll({ packetId, page })); + } + const results = await Promise.all(promises); + results.forEach(res => { + const data = res?.data?.data; + items = [...items, ...(data?.data || [])]; + }); + } + } catch (e) {} + setAllItemsMap(prev => ({ ...prev, [packetId]: items })); + return items; + }; + useEffect(() => { if (initialValues) { reset({ @@ -111,9 +187,15 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { if (initialValues.partyId) { setPartyId(initialValues.partyId); } + if (initialValues.paketIds) { + initialValues.paketIds.forEach((paket: any) => { + fetchAllItemsForPacket(paket.id); + }); + } } }, [initialValues, reset]); + // useFieldArray keyName="key" orqali unique key const { fields, append, remove } = useFieldArray({ control, name: 'packetItemDtos', @@ -165,21 +247,16 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { } }); - const handlePacketChange = (index: number, value: number) => { - setValue(`packetItemDtos.${index}.packetId`, value); - setValue(`packetItemDtos.${index}.itemDtos`, []); - }; - const appendPacket = () => { append({ packetId: 0, itemDtos: [] }); + setTimeout(() => { + document.activeElement instanceof HTMLElement && document.activeElement.blur(); + }, 0); }; const removePacket = (index: number) => { remove(index); }; - const [packetSearchTerm, setPacketSearchTerm] = useState(''); - - const packetSearchInputRef = useRef(null); const handlePartyChange = (event: any) => { const selectedParty = parties.find(p => p.id === event.target.value); @@ -190,113 +267,76 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { } }; - const [paketName, setPaketName] = useState(''); const PacketRow = ({ index, field }: { index: number; field: any }) => { const packetId = watch(`packetItemDtos.${index}.packetId`); - const [itemsPage, setItemsPage] = useState(1); - const [itemsList, setItemsList] = useState([]); - const [itemsHasMore, setItemsHasMore] = useState(true); - const itemScrollRef = useRef(null); - const [packetsPage, setPacketsPage] = useState(1); - const [packetsList, setPacketsList] = useState([]); - const [packetsHasMore, setPacketsHasMore] = useState(true); - const [selectedProductNames, setSelectedProductNames] = useState<{ [packetIndex: number]: { [productId: number]: string } }>({}); - const [paketName, setPaketName] = useState(''); - const [lastPacketId, setLastPacketId] = useState(null); - - const { isLoading: isLoadingProducts } = useQuery({ - queryKey: ['product-list', packetId, itemsPage], - queryFn: () => item_requests.getAll({ packetId, page: itemsPage }), - enabled: !!packetId, - onSuccess: data => { - const newItems = data?.data?.data?.data || []; - setItemsList(prev => (itemsPage === 1 ? newItems : [...prev, ...newItems])); - }, - }); - - const packetScrollRef = useRef(null); const [keyword, setKeyword] = useState(''); - const { isFetching: isLoadingPackets } = useQuery({ - queryKey: ['packets-list', partyId, keyword, packetsPage], - queryFn: () => box_requests.getAll({ partyId, cargoId: keyword, page: packetsPage }), - enabled: !!partyId, - onSuccess: data => { - const newPackets = data?.data?.data?.data || []; - setPacketsList(prev => (packetsPage === 1 ? newPackets : [...prev, ...newPackets])); - const totalPages = data?.data?.data?.totalPages || 0; - setPacketsHasMore(packetsPage < totalPages); - }, - }); + const [selectedProductNames, setSelectedProductNames] = useState<{ [productId: number]: string }>({}); + const packetsList = keyword ? allPackets.filter(p => (p.name || '').toLowerCase().includes(keyword.toLowerCase())) : allPackets; + const itemsList = allItemsMap[packetId] || []; + const loadingItems = !allItemsMap[packetId] && !!packetId; - const handlePacketScroll = (e: React.UIEvent) => { - const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; - const isNearBottom = scrollHeight - scrollTop - clientHeight < 50; - - if (isNearBottom && !isLoadingPackets && packetsHasMore) { - setPacketsPage(prev => prev + 1); + useEffect(() => { + if (packetId && !allItemsMap[packetId]) { + fetchAllItemsForPacket(packetId); } - }; + }, [packetId]); - const handleItemScroll = (e: React.UIEvent) => { - const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; - const isNearBottom = scrollHeight - scrollTop - clientHeight < 50; - - if (isNearBottom && !isLoadingProducts && itemsHasMore) { - setItemsPage(prev => prev + 1); + useEffect(() => { + if (packetId && field?.itemDtos?.length && itemsList.length) { + const names: { [productId: number]: string } = {}; + field.itemDtos.forEach((id: number) => { + const prod = itemsList.find(p => p.id === id); + names[id] = prod?.name || prod?.nameRu || String(id); + }); + setSelectedProductNames(names); } - }; + }, [field?.itemDtos, itemsList, packetId]); - const handleProductChange = (packetIndex: number, product: any, checked: boolean) => { + const handleProductChange = (product: any, checked: boolean) => { setSelectedProductNames(prev => { - const prevNames = prev[packetIndex] || {}; if (checked) { - return { ...prev, [packetIndex]: { ...prevNames, [product.id]: product.name || product.nameRu || String(product.id) } }; + return { ...prev, [product.id]: product.name || product.nameRu || String(product.id) }; } else { - const newNames = { ...prevNames }; + const newNames = { ...prev }; delete newNames[product.id]; - return { ...prev, [packetIndex]: newNames }; + return newNames; } }); }; + useEffect(() => { + const element = document.getElementById(`packet-select-${index}`); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + }, [fields.length]); + const handleSelectAllProducts = async () => { if (!packetId) return; - let allProducts: any[] = []; - let page = 1; - let totalPages = 1; - try { - do { - const res = await item_requests.list({ packetId, page }); - const data = res?.data?.data; - const products = data?.data || []; - totalPages = data?.totalPages || 1; - allProducts = [...allProducts, ...products]; - page++; - } while (page <= totalPages); - } catch (e) { - // error silent + let allProducts = allItemsMap[packetId] || []; + if (!allProducts.length) { + allProducts = await fetchAllItemsForPacket(packetId); } if (allProducts.length > 0) { setValue( `packetItemDtos.${index}.itemDtos`, allProducts.map((p: any) => p.id) ); - setSelectedProductNames((prev: any) => ({ - ...prev, - [index]: allProducts.reduce( + setSelectedProductNames( + allProducts.reduce( (acc, p) => ({ ...acc, [p.id]: p.name || p.nameRu || String(p.id), }), {} - ), - })); + ) + ); } }; const handleClearAll = () => { setValue(`packetItemDtos.${index}.itemDtos`, []); - setSelectedProductNames((prev: any) => ({ ...prev, [index]: {} })); + setSelectedProductNames({}); }; return ( @@ -315,59 +355,45 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { justifyContent={'space-between'} alignItems={'center'} > - setKeyword(e.target.value)} /> + setKeyword(e.target.value)} autoFocus={false} />
{t('packet')} ( - <> - { + e.stopPropagation(); + selectField.onChange(e); + }} + value={selectField.value || ''} + renderValue={selected => { + const selectedPacket = packetsList.find(p => p.id === selected); + return selectedPacket ? selectedPacket.name : t('loading'); + }} + > + {isLoadingPackets ? ( + + + + ) : packetsList.length === 0 ? ( + {t('not_found') || 'Paketlar topilmadi'} + ) : ( + packetsList.map(packet => ( + + {packet.name} - ) : ( - packetsList.map(packet => ( - setPaketName(packet.name)}> - {packet.name} - - )) - )} - {isLoadingPackets && packetsList.length > 0 && ( - - - - )} - - + )) + )} + )} /> {!!get(errors, `packetItemDtos.${index}.packetId`) && ( @@ -388,7 +414,6 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { )}
- {packetId && ( @@ -404,7 +429,7 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { - {isLoadingProducts && itemsList.length === 0 ? ( + {loadingItems ? ( ) : ( @@ -415,35 +440,25 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {