From eee4e05e27a792d95b666277389ee6067715ddbd Mon Sep 17 00:00:00 2001 From: Samandar Turg'unboev Date: Sat, 24 May 2025 09:29:47 +0500 Subject: [PATCH] real boxing create and edit components --- .../layout/dashboard-layout/routes.tsx | 16 +- src/data/real-box/real-box.model.ts | 74 ++++ src/data/real-box/real-box.requests.ts | 63 +++ .../boxes-create/DashboardCreateBox.tsx | 2 +- .../DashboardCreateRealBox.tsx | 395 +++++------------- .../real-boxes/DashboardRealBoxesPage.tsx | 3 +- 6 files changed, 262 insertions(+), 291 deletions(-) create mode 100644 src/data/real-box/real-box.model.ts create mode 100644 src/data/real-box/real-box.requests.ts diff --git a/src/components/layout/dashboard-layout/routes.tsx b/src/components/layout/dashboard-layout/routes.tsx index 8f04871..70f91b2 100644 --- a/src/components/layout/dashboard-layout/routes.tsx +++ b/src/components/layout/dashboard-layout/routes.tsx @@ -34,13 +34,13 @@ export const routes = [ roles: [UserRoleEnum.ADMIN, UserRoleEnum.CHINA_WORKER], }, { - title: 'boxes', - path: pageLinks.dashboard.real_boxes.index, + title: 'packet', + path: pageLinks.dashboard.boxes.index, icon: ( - + @@ -49,13 +49,13 @@ export const routes = [ roles: [UserRoleEnum.ADMIN, UserRoleEnum.CHINA_WORKER], }, { - title: 'packet', - path: pageLinks.dashboard.boxes.index, + title: 'boxes', + path: pageLinks.dashboard.real_boxes.index, icon: ( - + diff --git a/src/data/real-box/real-box.model.ts b/src/data/real-box/real-box.model.ts new file mode 100644 index 0000000..9737437 --- /dev/null +++ b/src/data/real-box/real-box.model.ts @@ -0,0 +1,74 @@ +export type BoxStatus = 'READY_TO_INVOICE' | 'READY'; +export const BoxStatusList: BoxStatus[] = ['READY_TO_INVOICE', 'READY']; + +export interface IBox { + packetNetWeight: string; + totalNetWeight: string; + cargoId: string; + passportName: string; + id: number; + partyName: string; + boxType: string; + name: string; + volume: string; + boxWeight: number; + brutto: number; + hasInvoice: boolean; + totalItems: number; + status: BoxStatus; + totalBrutto: number; +} + +export interface IBoxDetail { + packet: { + id: number; + cargoId: string; + packetNetWeight: number; + passportName: string; + totalItems: number; + totalNetWeight: number; + partyName: string; + partyId: string; + boxType: string; + name: string; + volume: string; + boxWeight: number; + brutto: number; + hasInvoice: boolean; + status: BoxStatus; + }; + client: { + passportId: number; + passportName: string; + }; + items: [ + { + id: number; + partyName: string; + boxName: string; + cargoId: string; + trekId: string; + name: string; + nameRu: string; + amount: number; + weight: number; + price: number; + totalPrice: number; + hasImage: boolean; + imageUrl: string | null; + packetName: string; + status: BoxStatus; + }, + ]; +} + +export type RealCreateBoxBodyType = { + partyName: string, + packetDtos: number[] +}; + +export type UpdateRealBoxBodyType = { + boxId: string, + partyName: string, + packetDtos: number[] +}; diff --git a/src/data/real-box/real-box.requests.ts b/src/data/real-box/real-box.requests.ts new file mode 100644 index 0000000..fe8f147 --- /dev/null +++ b/src/data/real-box/real-box.requests.ts @@ -0,0 +1,63 @@ +import { IBox, UpdateBoxBodyType, IBoxDetail, BoxStatus } from '@/data/box/box.model'; +import { CommonResponseType, PageAble } from '@/helpers/types'; +import { request } from '@/services/request'; +import axios from 'axios'; +import { RealCreateBoxBodyType, UpdateRealBoxBodyType } from './real-box.model'; + +export const real_box_requests = { + async getAll(params?: { + page?: number; + sort?: string; + direction?: string; + cargoId?: string; + partyId?: string | number; + status?: BoxStatus; + }) { + return request.get>>('/boxes/list', { params }); + }, + async create({ ...body }: RealCreateBoxBodyType) { + return request.post('/boxes/create', body); + }, + async update({ boxId, ...body }: UpdateRealBoxBodyType) { + return request.put('/packets/update', body, { + params: { boxId }, + }); + }, + async find(params: { packetId?: number | string }) { + return request.get>('/packets/find', { params }); + }, + async delete(params: { packetId: number | string }) { + return request.delete('/packets/delete', { params }); + }, + async downloadExcel(params: { packetId: number | string }) { + return request.get('/packets/download', { params, responseType: 'blob' }); + }, + async translateWithGoogleApi(params: { text: string }): Promise { + const response = await axios.post('https://translation.googleapis.com/language/translate/v2', undefined, { + params: { + q: params.text, + target: 'ru', + source: 'zh', + key: 'AIzaSyA5uAPZyjF_yo1hYOWWJ2uP7XgcmohZc8o', + }, + }); + + return response.data.data.translations?.[0]?.translatedText ?? ''; + }, + async translateWithMemoryApi(params: { text: string }): Promise { + const response = await axios.get<{ + responseData: { + translatedText: string; + match: number; + }; + }>('https://api.mymemory.translated.net/get', { + params: { + q: params.text, + langpair: 'zh|ru', + // key: '7a4ac2e07cde1ff1e9de', + }, + }); + + return response.data.responseData.translatedText; + }, +}; \ No newline at end of file diff --git a/src/routes/private/boxes-create/DashboardCreateBox.tsx b/src/routes/private/boxes-create/DashboardCreateBox.tsx index 7bf5327..8f4ade8 100644 --- a/src/routes/private/boxes-create/DashboardCreateBox.tsx +++ b/src/routes/private/boxes-create/DashboardCreateBox.tsx @@ -193,7 +193,7 @@ const DashboardCreateBoxPage = ({ initialValues, partiesData }: Props) => { }); useEffect(() => { - setValue('passportId', ''); + setValue('passportId', 'AA1234567'); }, [cargoId]); const { data: defaultParties, status: defaultPartiesStatus } = useRequest(() => party_requests.getAll({ status: 'COLLECTING' }), { diff --git a/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx b/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx index 7c42e85..9e595ca 100644 --- a/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx +++ b/src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx @@ -1,33 +1,25 @@ '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, useSearchParams, useRouter } from 'next/navigation'; +import { notifyUnknownError } from '@/services/notification'; +import { Box, FormHelperText, Grid, Stack, Typography, styled } from '@mui/material'; +import { 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 BaseReactSelect, { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect'; -import { customer_requests } from '@/data/customers/customer.requests'; +import { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect'; import { useAuthContext } from '@/context/auth-context'; import { useMyNavigation } from '@/hooks/useMyNavigation'; import AsyncSelect from 'react-select/async'; -import cloneDeep from 'lodash.clonedeep'; -import { item_requests } from '@/data/item/item.requests'; +import { box_requests } from '@/data/box/box.requests'; +import { real_box_requests } from '@/data/real-box/real-box.requests'; + import get from 'lodash.get'; -import Loader from '@/components/common/Loader'; -import { Customer } from '@/data/customers/customer.model'; -import { Passport } from '@/data/passport/passport.model'; -import { passport_requests } from '@/data/passport/passport.request'; const StyledCreateBox = styled(Box)` .item-row { @@ -37,9 +29,6 @@ const StyledCreateBox = styled(Box)` gap: 16px; } - .item-row-field { - } - & > * { flex: 1 1 1; } @@ -49,20 +38,11 @@ type Props = { partiesData?: { value: number; label: string }[]; initialValues?: { id: number; + boxId: string; 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; + packetId: string; products_list: { id: number; price: number | string; @@ -77,31 +57,30 @@ type Props = { }; const DashboardCreateRealBoxPage = ({ 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 [partyId, setPartyId] = useState(""); + const [loading, setLoading] = useState(false); const helperRef = useRef<{ finished: boolean; partyFinished: boolean; clientFinished: boolean; settedDefaultParty: any; - settedDefaultClient: Customer | null; }>({ settedDefaultParty: null, - settedDefaultClient: null, partyFinished: false, clientFinished: false, finished: false, }); + const { register, control, handleSubmit, - watch, setValue, formState: { errors }, } = useForm({ @@ -112,80 +91,46 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { : params.get('party_id') ? [{ id: +params.get('party_id')! }] : [{ id: '' }], - box_weight: 0.9, - box_type: 'KG', - box_size: '50x40x40', - status: 'READY_TO_INVOICE', - cargoId: initialValues?.client_id, - products_list: editMode - ? initialValues?.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 pakets = useFieldArray({ control, name: 'paketIds', 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); - const passportOptionsInitial = initialValues?.passportId && - initialValues?.passportName && [ - { - value: initialValues?.passportId, - label: initialValues?.passportName, + const getBoxesQuery = useRequest( + () => + box_requests.getAll({ + partyId: partyId, + }), + { + selectData(data) { + return data?.data?.data; }, - ]; + } + ); - const { data: passportOptions } = useRequest(() => passport_requests.getAll({ cargoId: cargoId?.toUpperCase() }), { - enabled: !!cargoId, - selectData: data => { - const passportOptions = data.data.data.map((passport: Passport) => ({ - value: passport.id, - label: passport.fullName, - })); - - 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: [], - }); + const { data: list } = useMemo(() => { + if (getBoxesQuery.data?.data) { + return { + data: getBoxesQuery.data.data.filter((box: any) => box.status === 'READY_TO_INVOICE'), + }; + } + return { data: [] }; + }, [getBoxesQuery, partyId]); useEffect(() => { - setValue('passportId', ''); - }, [cargoId]); + if (partyId) { + getBoxesQuery.refetch(); + } + }, [partyId]); - const { data: defaultParties, status: defaultPartiesStatus } = useRequest(() => party_requests.getAll({ status: 'COLLECTING' }), { + const { data: defaultParties } = useRequest(() => party_requests.getAll({ status: 'COLLECTING' }), { enabled: true, selectData(data) { return data.data.data.data.map(p => ({ value: p.id, label: p.name })); @@ -204,98 +149,24 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { 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 })); - }, - 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) => { - setSelectedPassport(newValue); - if (newValue) { - setValue('passport_id', newValue.id || null); - } else { - setValue('passport_id', null); - } - }; - const onSubmit = handleSubmit(async values => { try { setLoading(true); + const packetDtos = values.paketIds.map((paket: any) => paket.id).filter((id: any) => id); if (editMode) { - const updateBody: UpdateBoxBodyType = { - cargoId: values.client_id, - passportId: values.passportId?.value, - status: values.status, - packetId: values.paketIds.map((p: any) => p.id), // Bir nechta paketId - 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 updateBody: UpdateRealBoxBodyType = { + boxId: initialValues?.boxId, + partyName: values.partyName, + packetDtos, }; - - 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); + await real_box_requests.update(updateBody); } else { - const createBody: CreateBoxBodyType = { - status: values.status, - cargoId: values.cargoId, - passportId: values.passportId?.value, - partyId: values.partyId, - items: values.products_list.map((item: any) => { - return { - trekId: item.trekId, - name: item.name, - weight: +item.weight, - amount: +item.amount, - }; - }), + const createBody: RealCreateBoxBodyType = { + partyName: values.partyName, + packetDtos, }; - await box_requests.create(createBody); + await real_box_requests.create(createBody); } navigation.push(pageLinks.dashboard.boxes.index); @@ -320,67 +191,6 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { pakets.remove(index); }; - 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.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 ( { name='partyId' control={control} rules={{ required: requiredText }} - render={({ field, fieldState, formState }) => { - return ( - { - field.onChange(newValue.value); - }} - defaultValue={ - editMode + render={({ field }) => ( + { + field.onChange(newValue.value); + setPartyId(newValue.value); + }} + defaultValue={ + editMode + ? { + value: initialValues.partyId, + label: initialValues.partyName, + } + : partiesData?.length ? { - value: initialValues.partyId, - label: initialValues.partyName, + value: partiesData[0].value, + label: partiesData[0].label, } - : 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')} - /> - ); - }} + : 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')} + /> + )} /> - {/* {!!errors.partyId?.message && ( - {errors.partyId?.message} - )} */} @@ -460,32 +266,48 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { name={`paketIds.${index}.id`} control={control} rules={{ required: requiredText }} - render={({ field: paketField, fieldState }) => ( + render={({ field: paketField }) => ( { paketField.onChange(newValue?.value); }} defaultValue={ - editMode && index === 0 + editMode && index === 0 && initialValues.packetId ? { - value: initialValues.partyId, - label: initialValues.partyName, + value: initialValues.packetId, + label: initialValues.box_name || `Box ${initialValues.packetId}`, } - : partiesData?.length && index === 0 - ? { - value: partiesData[0].value, - label: partiesData[0].label, - } - : null + : null } styles={selectDefaultStyles} noOptionsMessage={() => t('not_found')} loadingMessage={() => t('loading')} onBlur={paketField.onBlur} name={paketField.name} - defaultOptions={defaultParties!} - loadOptions={partyOptions} - placeholder={t('enter_party_name_to_find')} + defaultOptions={ + list.length > 0 + ? list.map((box: any) => ({ + value: box.id, + label: box.box_name || box.name || `Box ${box.id}`, + })) + : [] + } + loadOptions={async (inputValue: string) => { + if (!partyId || partyId === '') return []; + try { + const res = await box_requests.getAll({ + partyId: partyId, + }); + return res.data.data.data.map((box: any) => ({ + label: box.box_name || box.name || `Box ${box.id}`, + value: box.id, + })); + } catch (error) { + notifyUnknownError(error); + return []; + } + }} + placeholder={t('enter_box_name_to_find')} /> )} /> @@ -528,4 +350,15 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => { ); }; -export default DashboardCreateRealBoxPage; \ No newline at end of file +export default DashboardCreateRealBoxPage; + +export type RealCreateBoxBodyType = { + partyName: string; + packetDtos: number[]; +}; + +export type UpdateRealBoxBodyType = { + boxId: string; + partyName: string; + packetDtos: number[]; +}; \ No newline at end of file diff --git a/src/routes/private/real-boxes/DashboardRealBoxesPage.tsx b/src/routes/private/real-boxes/DashboardRealBoxesPage.tsx index 86ed532..173897f 100644 --- a/src/routes/private/real-boxes/DashboardRealBoxesPage.tsx +++ b/src/routes/private/real-boxes/DashboardRealBoxesPage.tsx @@ -10,6 +10,7 @@ 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 { real_box_requests } from '@/data/real-box/real-box.requests'; import { DEFAULT_PAGE_SIZE, pageLinks } from '@/helpers/constants'; import useDebouncedInput from '@/hooks/useDebouncedInput'; import useInput from '@/hooks/useInput'; @@ -49,7 +50,7 @@ const DashboardRealBoxesPage = (props: Props) => { const getBoxesQuery = useRequest( () => - box_requests.getAll({ + real_box_requests.getAll({ page: page, cargoId: keyword, status: boxStatusFilter,