Merge branch 'az' into 'main'
feat: real boxes page added See merge request azizziy/cpost!1
This commit is contained in:
9
src/app/[locale]/dashboard/boxes/create/loading.tsx
Normal file
9
src/app/[locale]/dashboard/boxes/create/loading.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { CircularProgress, Stack } from '@mui/material';
|
||||||
|
|
||||||
|
export default function DashboardLoading() {
|
||||||
|
return (
|
||||||
|
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
|
||||||
|
<CircularProgress size={'64px'} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
src/app/[locale]/dashboard/boxes/create/page.tsx
Normal file
22
src/app/[locale]/dashboard/boxes/create/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import useRequest from '@/hooks/useRequest';
|
||||||
|
import { party_requests } from '@/data/party/party.requests';
|
||||||
|
import Loader from '@/components/common/Loader';
|
||||||
|
import React from 'react';
|
||||||
|
import DashboardCreateRealBoxPage from '@/routes/private/real-boxes-create/DashboardCreateRealBox';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const partiesData = useRequest(() => party_requests.getAll({ status: 'COLLECTING' }), {
|
||||||
|
selectData(data) {
|
||||||
|
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
|
||||||
|
},
|
||||||
|
placeholderData: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (partiesData.loading || !partiesData.data) {
|
||||||
|
return <Loader p={8} size={96} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <DashboardCreateRealBoxPage partiesData={partiesData.data} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { CircularProgress, Stack } from '@mui/material';
|
||||||
|
|
||||||
|
export default function DashboardLoading() {
|
||||||
|
return (
|
||||||
|
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
|
||||||
|
<CircularProgress size={'64px'} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
src/app/[locale]/dashboard/boxes/edit/[box_id]/page.tsx
Normal file
5
src/app/[locale]/dashboard/boxes/edit/[box_id]/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import DashboardEditRealBoxPage from '@/routes/private/real-boxes-create/DashboardEditRealBox';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return <DashboardEditRealBoxPage />;
|
||||||
|
}
|
||||||
5
src/app/[locale]/dashboard/boxes/page.tsx
Normal file
5
src/app/[locale]/dashboard/boxes/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import DashboardRealBoxesPage from "@/routes/private/real-boxes/DashboardRealBoxesPage";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return <DashboardRealBoxesPage />;
|
||||||
|
}
|
||||||
@@ -33,6 +33,21 @@ export const routes = [
|
|||||||
),
|
),
|
||||||
roles: [UserRoleEnum.ADMIN, UserRoleEnum.CHINA_WORKER],
|
roles: [UserRoleEnum.ADMIN, UserRoleEnum.CHINA_WORKER],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'boxes',
|
||||||
|
path: pageLinks.dashboard.real_boxes.index,
|
||||||
|
icon: (
|
||||||
|
<SvgIcon fontSize='small'>
|
||||||
|
<svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||||
|
<path
|
||||||
|
d='M19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3ZM19 14H15.44C15.08 14 14.76 14.19 14.58 14.5C14.06 15.4 13.11 16 12 16C10.89 16 9.94 15.4 9.42 14.5C9.24 14.19 8.91 14 8.56 14H5V5H19V14ZM14.79 10H13V7C13 6.45 12.55 6 12 6C11.45 6 11 6.45 11 7V10H9.21C8.76 10 8.54 10.54 8.86 10.85L11.65 13.64C11.85 13.84 12.16 13.84 12.36 13.64L15.15 10.85C15.46 10.54 15.24 10 14.79 10Z'
|
||||||
|
fill='currentColor'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</SvgIcon>
|
||||||
|
),
|
||||||
|
roles: [UserRoleEnum.ADMIN, UserRoleEnum.CHINA_WORKER],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'packet',
|
title: 'packet',
|
||||||
path: pageLinks.dashboard.boxes.index,
|
path: pageLinks.dashboard.boxes.index,
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export const pageLinks = {
|
|||||||
create: '/dashboard/packets/create',
|
create: '/dashboard/packets/create',
|
||||||
edit: (slug: string | number) => '/dashboard/packets/edit/' + slug,
|
edit: (slug: string | number) => '/dashboard/packets/edit/' + slug,
|
||||||
},
|
},
|
||||||
|
real_boxes: {
|
||||||
|
index: '/dashboard/boxes',
|
||||||
|
create: '/dashboard/boxes/create',
|
||||||
|
edit: (slug: string | number) => '/dashboard/boxes/edit/' + slug,
|
||||||
|
},
|
||||||
items: {
|
items: {
|
||||||
index: '/dashboard/items',
|
index: '/dashboard/items',
|
||||||
create: '/dashboard/items/create',
|
create: '/dashboard/items/create',
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ const DashboardBoxesPage = (props: Props) => {
|
|||||||
color: '#000',
|
color: '#000',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('packets')}
|
{t('packet')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack direction={'row'} alignItems={'center'} spacing={2}>
|
<Stack direction={'row'} alignItems={'center'} spacing={2}>
|
||||||
<BaseInput
|
<BaseInput
|
||||||
|
|||||||
752
src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx
Normal file
752
src/routes/private/real-boxes-create/DashboardCreateRealBox.tsx
Normal file
@@ -0,0 +1,752 @@
|
|||||||
|
'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 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 { 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 {
|
||||||
|
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 DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||||
|
const [cargoIdValue, setCargoIdValue] = useState<string>('');
|
||||||
|
const { user, isAdmin: isAdminUser } = useAuthContext();
|
||||||
|
const editMode = !!initialValues && !!initialValues.id;
|
||||||
|
const isAdmin = isAdminUser && editMode;
|
||||||
|
const t = useMyTranslation();
|
||||||
|
const params = useSearchParams();
|
||||||
|
const navigation = useMyNavigation();
|
||||||
|
const helperRef = useRef<{
|
||||||
|
finished: boolean;
|
||||||
|
partyFinished: boolean;
|
||||||
|
clientFinished: boolean;
|
||||||
|
settedDefaultParty: Party | null;
|
||||||
|
settedDefaultClient: Customer | null;
|
||||||
|
// settedDefaultPartyValue: { value: number; label: string }[] | null;
|
||||||
|
}>({
|
||||||
|
settedDefaultParty: null,
|
||||||
|
settedDefaultClient: null,
|
||||||
|
partyFinished: false,
|
||||||
|
clientFinished: false,
|
||||||
|
finished: false,
|
||||||
|
// settedDefaultPartyValue: partiesData?.[0] || null,
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
setValue,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<any>({
|
||||||
|
defaultValues: {
|
||||||
|
partyId: params.get('party_id') ? +params.get('party_id')! : '',
|
||||||
|
box_weight: 0.9,
|
||||||
|
box_type: 'KG',
|
||||||
|
box_size: '50x40x40',
|
||||||
|
status: 'READY_TO_INVOICE',
|
||||||
|
cargoId: initialValues?.client_id,
|
||||||
|
// passportId: {value: initialValues?.passportId},
|
||||||
|
...(editMode
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
products_list: [
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
cargoId: '',
|
||||||
|
trekId: '',
|
||||||
|
name: '',
|
||||||
|
nameRu: '',
|
||||||
|
amount: '',
|
||||||
|
weight: '',
|
||||||
|
price: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
...initialValues,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const products = useFieldArray({
|
||||||
|
control,
|
||||||
|
name: 'products_list',
|
||||||
|
keyName: 'key',
|
||||||
|
});
|
||||||
|
const controlledProductFields = watch('products_list');
|
||||||
|
const partyIdValue = watch('partyId');
|
||||||
|
const clientIdValue = watch('client_id');
|
||||||
|
const cargoId = watch('cargoId');
|
||||||
|
const requiredText = t('required');
|
||||||
|
const [selectedPassport, setSelectedPassport] = useState<Passport | null>(null); // Tanlangan passportni saqlash uchun state (tipi Passport yoki null)
|
||||||
|
|
||||||
|
const passportOptionsInitial = initialValues?.passportId &&
|
||||||
|
initialValues?.passportName && [
|
||||||
|
{
|
||||||
|
value: initialValues?.passportId,
|
||||||
|
label: initialValues?.passportName,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const n = "123ds"
|
||||||
|
n.toUpperCase()
|
||||||
|
const { data: passportOptions } = useRequest(() => passport_requests.getAll({ cargoId: cargoId?.toUpperCase() }), {
|
||||||
|
enabled: !!cargoId,
|
||||||
|
selectData: data => {
|
||||||
|
// Ma'lumotlarni BaseReactSelect uchun mos formatga o'tkazish
|
||||||
|
const passportOptions = data.data.data.map((passport: Passport) => ({
|
||||||
|
// data.data endi Passport[]
|
||||||
|
value: passport.id, // passport id sini value sifatida
|
||||||
|
label: passport.fullName, // fullName ni label sifatida
|
||||||
|
}));
|
||||||
|
|
||||||
|
const passportId = watch('passportId');
|
||||||
|
|
||||||
|
if (!passportId && initialValues?.passportId && cargoId === initialValues?.client_id) {
|
||||||
|
const currentOption = passportOptions?.find((item: { value: number }) => item.value === initialValues?.passportId);
|
||||||
|
setValue('passportId', currentOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
return passportOptions;
|
||||||
|
},
|
||||||
|
dependencies: [cargoId],
|
||||||
|
placeholderData: [], // Kerak emas, chunki server PageAble qaytarmayapti
|
||||||
|
onSuccess(data) {
|
||||||
|
// if (data?.data.data?.[0]?.id) {
|
||||||
|
// setValue("passportId", initialValues?.passportId)
|
||||||
|
// setValue('passport_id', data.data.data[0].id);
|
||||||
|
// setSelectedPassport(data.data.data[0]); // Birinchi elementni tanlash
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue('passportId', '');
|
||||||
|
}, [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: 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,
|
||||||
|
status: values.status,
|
||||||
|
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,
|
||||||
|
// cargoId: item.cargoId,
|
||||||
|
trekId: item.trekId,
|
||||||
|
// name: item.name + (item.nameRu ? ` / ${item.nameRu}` : ''),
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
await box_requests.create(createBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigation.push(pageLinks.dashboard.boxes.index);
|
||||||
|
} catch (error) {
|
||||||
|
notifyUnknownError(error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const partyOptions = (inputValue: string) => {
|
||||||
|
return party_requests.getAll({ status: 'COLLECTING', partyName: inputValue }).then(res => {
|
||||||
|
return res.data.data.data.map(p => ({ label: p.name, value: p.id }));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// const clientOptions = (inputValue: string) => {
|
||||||
|
// return customer_requests.getAll({ clientName: inputValue, page: 1 }).then(res => {
|
||||||
|
// return res.data.data.data.map(p => ({ label: p.fullName, value: p.id }));
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
const appendProduct = () => {
|
||||||
|
products.append({
|
||||||
|
id: '',
|
||||||
|
cargoId: '',
|
||||||
|
trekId: '',
|
||||||
|
name: '',
|
||||||
|
amount: '',
|
||||||
|
weight: '',
|
||||||
|
price: '',
|
||||||
|
totalPrice: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeProduct = (index: number) => {
|
||||||
|
products.remove(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const translateAndUpdateRussianName = async (text: string, index: number) => {
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// const responseText = await box_requests.translateWithGoogleApi({ text });
|
||||||
|
const responseText = await box_requests.translateWithMemoryApi({ text });
|
||||||
|
setValue(`products_list.${index}.nameRu`, responseText || '');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
notifyError('Translation api error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const boxTypes = [
|
||||||
|
{
|
||||||
|
label: 'KG',
|
||||||
|
value: 'KG',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'GABARIT',
|
||||||
|
value: 'GABARIT',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const boxStatuses = useMemo(() => {
|
||||||
|
const p: {
|
||||||
|
label: string;
|
||||||
|
value: BoxStatus;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
label: t('READY_TO_INVOICE'),
|
||||||
|
value: 'READY_TO_INVOICE',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isAdmin) {
|
||||||
|
p.push({
|
||||||
|
label: t('READY'),
|
||||||
|
value: 'READY',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}, [isAdmin]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledCreateBox
|
||||||
|
width={1}
|
||||||
|
mb={3}
|
||||||
|
sx={{
|
||||||
|
padding: '28px',
|
||||||
|
borderRadius: '16px',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box component={'form'} onSubmit={onSubmit}>
|
||||||
|
<Typography variant='h5' mb={3.5}>
|
||||||
|
{editMode ? t('update_packet') : t('create_packet')}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Grid container columnSpacing={2.5} rowSpacing={3} mb={3.5}>
|
||||||
|
<Grid item xs={5}>
|
||||||
|
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
|
||||||
|
{t('party_name')}
|
||||||
|
</Typography>
|
||||||
|
<Controller
|
||||||
|
name='partyId'
|
||||||
|
control={control}
|
||||||
|
rules={{ required: requiredText }}
|
||||||
|
render={({ field, fieldState, formState }) => {
|
||||||
|
return (
|
||||||
|
<AsyncSelect
|
||||||
|
onChange={(newValue: any) => {
|
||||||
|
field.onChange(newValue.value);
|
||||||
|
}}
|
||||||
|
defaultValue={
|
||||||
|
editMode
|
||||||
|
? {
|
||||||
|
value: initialValues.partyId,
|
||||||
|
label: initialValues.partyName,
|
||||||
|
}
|
||||||
|
: partiesData?.length
|
||||||
|
? {
|
||||||
|
value: partiesData[0].value,
|
||||||
|
label: partiesData[0].label,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
styles={selectDefaultStyles}
|
||||||
|
noOptionsMessage={() => t('not_found')}
|
||||||
|
loadingMessage={() => t('loading')}
|
||||||
|
onBlur={field.onBlur}
|
||||||
|
name={field.name}
|
||||||
|
defaultOptions={defaultParties!}
|
||||||
|
loadOptions={partyOptions}
|
||||||
|
placeholder={t('enter_party_name_to_find')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* @ts-expect-error */}
|
||||||
|
{!!errors.partyId?.message && <FormHelperText sx={{ color: 'red' }}>{errors.partyId?.message}</FormHelperText>}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={5}>
|
||||||
|
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
|
||||||
|
{t('status')}
|
||||||
|
</Typography>
|
||||||
|
<Controller
|
||||||
|
name='status'
|
||||||
|
control={control}
|
||||||
|
rules={{ required: requiredText }}
|
||||||
|
render={({ field, fieldState, formState }) => {
|
||||||
|
return (
|
||||||
|
<BaseReactSelect
|
||||||
|
value={boxStatuses?.find(p => p.value === field.value)}
|
||||||
|
onChange={(newValue: any) => {
|
||||||
|
field.onChange(newValue.value);
|
||||||
|
}}
|
||||||
|
onBlur={field.onBlur}
|
||||||
|
name={field.name}
|
||||||
|
options={boxStatuses}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* @ts-expect-error */}
|
||||||
|
{!!errors.box_type?.message && <FormHelperText sx={{ color: 'red' }}>{errors.box_type?.message}</FormHelperText>}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={5}>
|
||||||
|
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
|
||||||
|
{t('cargo_id')}
|
||||||
|
</Typography>
|
||||||
|
<BaseInput
|
||||||
|
disabled={!!initialValues}
|
||||||
|
type='text'
|
||||||
|
fullWidth
|
||||||
|
inputProps={{
|
||||||
|
step: 0.1,
|
||||||
|
}}
|
||||||
|
mainBorderColor='#D8D8D8'
|
||||||
|
placeholder={t('cargo_id')}
|
||||||
|
{...register('cargoId')}
|
||||||
|
/>
|
||||||
|
{!!errors.net_weight?.message && (
|
||||||
|
// @ts-expect-error
|
||||||
|
<FormHelperText sx={{ color: 'red' }}>{errors.net_weight?.message}</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={5}>
|
||||||
|
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
|
||||||
|
{t('passport')}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name='passportId'
|
||||||
|
control={control}
|
||||||
|
rules={{ required: requiredText }}
|
||||||
|
render={({ field, fieldState, formState }) => {
|
||||||
|
return (
|
||||||
|
<BaseReactSelect
|
||||||
|
// value={selectedPassport}
|
||||||
|
// onChange={onPassportChange}
|
||||||
|
|
||||||
|
// value={field.value}
|
||||||
|
{...field}
|
||||||
|
onChange={(newValue: any) => {
|
||||||
|
onPassportChange(newValue);
|
||||||
|
field.onChange(newValue);
|
||||||
|
}}
|
||||||
|
// options={passportOptions || passportOptionsInitial || []}
|
||||||
|
options={passportOptions || passportOptionsInitial || []}
|
||||||
|
// isLoading={passportLoading}
|
||||||
|
placeholder={t('passport')}
|
||||||
|
isDisabled={!clientIdValue || !!initialValues}
|
||||||
|
noOptionsMessage={() => t('not_found')}
|
||||||
|
loadingMessage={() => t('loading')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Stack
|
||||||
|
sx={{
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '2px solid #3489E4',
|
||||||
|
background: '#FFF',
|
||||||
|
padding: '24px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{controlledProductFields.map((product: any, index: number) => {
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
let totalPrice = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const p = +product.price * +product.amount;
|
||||||
|
if (!Number.isNaN(p)) {
|
||||||
|
totalPrice = p;
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box key={product.key} mb={1.5}>
|
||||||
|
<Box className='item-row' mb={1.5}>
|
||||||
|
<Box className='item-row-field'>
|
||||||
|
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
|
||||||
|
{t('track_id')}
|
||||||
|
</Typography>
|
||||||
|
<BaseInput
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#EBEFF5',
|
||||||
|
color: '#929191',
|
||||||
|
alignSelf: 'stretch',
|
||||||
|
height: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
pl: '10px',
|
||||||
|
pr: '10px',
|
||||||
|
borderRadius: '8px 0 0 8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>ID</span>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
placeholder={t('id')}
|
||||||
|
sx={{
|
||||||
|
'.MuiInputBase-root': {
|
||||||
|
paddingLeft: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...register(`products_list.${index}.trekId`, { required: requiredText })}
|
||||||
|
/>
|
||||||
|
{!!get(errors, `products_list.${index}.trekId`) && (
|
||||||
|
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box className='item-row-field'>
|
||||||
|
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
|
||||||
|
{t('name')}
|
||||||
|
</Typography>
|
||||||
|
<BaseInput
|
||||||
|
fullWidth
|
||||||
|
mainBorderColor='#D8D8D8'
|
||||||
|
placeholder={t('name')}
|
||||||
|
{...register(`products_list.${index}.name`, { required: requiredText })}
|
||||||
|
onBlur={event => {
|
||||||
|
translateAndUpdateRussianName(event.target.value, index);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!!get(errors, `products_list.${index}.name`) && (
|
||||||
|
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box className='item-row-field'>
|
||||||
|
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
|
||||||
|
{'NAME_RU'}
|
||||||
|
</Typography>
|
||||||
|
<BaseInput
|
||||||
|
// disabled
|
||||||
|
fullWidth
|
||||||
|
mainBorderColor='#D8D8D8'
|
||||||
|
placeholder={t('name')}
|
||||||
|
{...register(`products_list.${index}.nameRu`)}
|
||||||
|
/>
|
||||||
|
{!!get(errors, `products_list.${index}.name`) && (
|
||||||
|
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box className='item-row-field'>
|
||||||
|
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
|
||||||
|
{t('quantity')}
|
||||||
|
</Typography>
|
||||||
|
<BaseInput
|
||||||
|
fullWidth
|
||||||
|
type='number'
|
||||||
|
mainBorderColor='#D8D8D8'
|
||||||
|
placeholder={t('quantity')}
|
||||||
|
{...register(`products_list.${index}.amount`, { required: requiredText })}
|
||||||
|
/>
|
||||||
|
{!!get(errors, `products_list.${index}.amount`) && (
|
||||||
|
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box className='item-row-field'>
|
||||||
|
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
|
||||||
|
{t('weight')}
|
||||||
|
</Typography>
|
||||||
|
<BaseInput
|
||||||
|
fullWidth
|
||||||
|
type='number'
|
||||||
|
inputProps={{ step: 'any', min: 0, type: 'number' }}
|
||||||
|
mainBorderColor='#D8D8D8'
|
||||||
|
placeholder={t('weight')}
|
||||||
|
{...register(`products_list.${index}.weight`, { required: requiredText })}
|
||||||
|
/>
|
||||||
|
{!!get(errors, `products_list.${index}.amount`) && (
|
||||||
|
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{isAdmin && (
|
||||||
|
<React.Fragment>
|
||||||
|
<Box className='item-row-field'>
|
||||||
|
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
|
||||||
|
{t('weight')}
|
||||||
|
</Typography>
|
||||||
|
<Stack direction={'row'} alignItems={'center'} spacing={2.5}>
|
||||||
|
<BaseInput
|
||||||
|
fullWidth
|
||||||
|
type='text'
|
||||||
|
inputProps={{
|
||||||
|
step: 0.1,
|
||||||
|
}}
|
||||||
|
mainBorderColor='#D8D8D8'
|
||||||
|
placeholder={t('weight')}
|
||||||
|
{...register(`products_list.${index}.weight`)}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
<Box className='item-row-field'>
|
||||||
|
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
|
||||||
|
{t('price')}
|
||||||
|
</Typography>
|
||||||
|
<BaseInput
|
||||||
|
fullWidth
|
||||||
|
type='text'
|
||||||
|
inputProps={{
|
||||||
|
step: 0.1,
|
||||||
|
}}
|
||||||
|
mainBorderColor='#D8D8D8'
|
||||||
|
placeholder={t('price')}
|
||||||
|
{...register(`products_list.${index}.price`, { required: requiredText })}
|
||||||
|
/>
|
||||||
|
{!!get(errors, `products_list.${index}.price`) && (
|
||||||
|
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box className='item-row-field'>
|
||||||
|
<Typography fontSize={'18px'} fontWeight={500} color='#5D5850' mb={2}>
|
||||||
|
{t('total_price')}
|
||||||
|
</Typography>
|
||||||
|
<BaseInput
|
||||||
|
fullWidth
|
||||||
|
type='number'
|
||||||
|
inputProps={{
|
||||||
|
step: 0.001,
|
||||||
|
}}
|
||||||
|
value={totalPrice}
|
||||||
|
mainBorderColor='#D8D8D8'
|
||||||
|
placeholder={t('total_price')}
|
||||||
|
// {...register(`products_list.${index}.totalPrice`, { required: requiredText })}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box className='item-row-field'>
|
||||||
|
<BaseIconButton
|
||||||
|
size='small'
|
||||||
|
colorVariant='icon-error'
|
||||||
|
sx={{ flexShrink: 0, height: 'auto', marginTop: 4.5 }}
|
||||||
|
onClick={() => removeProduct(index)}
|
||||||
|
>
|
||||||
|
<Close />
|
||||||
|
</BaseIconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Divider color='#EBEFF6' />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<Stack alignItems={'center'}>
|
||||||
|
<BaseButton sx={{ backgroundColor: '#239D5F' }} startIcon={<AddCircleRounded />} onClick={appendProduct}>
|
||||||
|
{t('add_more')}
|
||||||
|
</BaseButton>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<BaseButton type='submit' colorVariant='blue' loading={loading}>
|
||||||
|
{editMode ? t('update') : t('create')}
|
||||||
|
</BaseButton>
|
||||||
|
</Box>
|
||||||
|
</StyledCreateBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardCreateRealBoxPage;
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Loader from '@/components/common/Loader';
|
||||||
|
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 = {};
|
||||||
|
|
||||||
|
const DashboardEditRealBoxPage = (props: Props) => {
|
||||||
|
const params = useParams();
|
||||||
|
const box_id = params.box_id as string;
|
||||||
|
|
||||||
|
const getOneBox = useRequest(
|
||||||
|
() => {
|
||||||
|
return box_requests.find({ packetId: box_id });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selectData(data) {
|
||||||
|
const boxData = data.data.data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: +box_id,
|
||||||
|
box_name: boxData.packet.name,
|
||||||
|
net_weight: +boxData.packet.brutto,
|
||||||
|
box_weight: +boxData.packet.boxWeight,
|
||||||
|
box_type: boxData.packet.boxType,
|
||||||
|
box_size: boxData.packet.volume,
|
||||||
|
passportName: boxData.packet.passportName,
|
||||||
|
status: boxData.packet.status,
|
||||||
|
packetId: box_id,
|
||||||
|
|
||||||
|
partyId: +boxData.packet.partyId,
|
||||||
|
partyName: boxData.packet.partyName,
|
||||||
|
|
||||||
|
// client_id: boxData.client?.passportId,
|
||||||
|
passportId: boxData.client?.passportId,
|
||||||
|
client_id: boxData.packet?.cargoId,
|
||||||
|
clientId: boxData.client?.passportId,
|
||||||
|
clientName: boxData.client?.passportName,
|
||||||
|
|
||||||
|
products_list: [
|
||||||
|
...boxData.items.map(item => {
|
||||||
|
let name = item.name;
|
||||||
|
let nameRu = item.nameRu;
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// name = item.name.split(' / ')[0];
|
||||||
|
// nameRu = item.name.split(' / ')[1];
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('prepare edit values error', error);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
price: item.price,
|
||||||
|
|
||||||
|
cargoId: item.cargoId,
|
||||||
|
trekId: item.trekId,
|
||||||
|
name: name,
|
||||||
|
nameRu: nameRu,
|
||||||
|
amount: +item.amount,
|
||||||
|
weight: +item.weight,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (getOneBox.loading || !getOneBox.data) {
|
||||||
|
return <Loader p={8} size={96} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DashboardCreateBoxPage initialValues={getOneBox.data} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardEditRealBoxPage;
|
||||||
1
src/routes/private/real-boxes-create/index.ts
Normal file
1
src/routes/private/real-boxes-create/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './DashboardCreateRealBox';
|
||||||
368
src/routes/private/real-boxes/DashboardRealBoxesPage.tsx
Normal file
368
src/routes/private/real-boxes/DashboardRealBoxesPage.tsx
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import ActionPopMenu from '@/components/common/ActionPopMenu';
|
||||||
|
import { MyTable, ColumnData } 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 { useAuthContext } from '@/context/auth-context';
|
||||||
|
import { BoxStatus, BoxStatusList, IBox } from '@/data/box/box.model';
|
||||||
|
import { box_requests } from '@/data/box/box.requests';
|
||||||
|
import { DEFAULT_PAGE_SIZE, pageLinks } from '@/helpers/constants';
|
||||||
|
import useDebouncedInput from '@/hooks/useDebouncedInput';
|
||||||
|
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 { getBoxStatusStyles, getStatusColor } from '@/theme/getStatusBoxStyles';
|
||||||
|
import { Add, AddCircleOutline, Circle, Delete, Download, Edit, FilterList, FilterListOff, Search, PlusOne } from '@mui/icons-material';
|
||||||
|
import { Box, Button, Stack, Tooltip, Typography } from '@mui/material';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
|
||||||
|
const DashboardRealBoxesPage = (props: Props) => {
|
||||||
|
const t = useMyTranslation();
|
||||||
|
const navigation = useMyNavigation();
|
||||||
|
const { isAdmin } = useAuthContext();
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||||
|
const { value: keyword, onChange: handleKeyword, setValue: setKeyword } = useInput('');
|
||||||
|
const [boxStatusFilter, setBoxStatusFilter] = useState<BoxStatus | undefined>(undefined);
|
||||||
|
|
||||||
|
const [deleteIds, setDeleteIds] = useState<number[]>([]);
|
||||||
|
const [downloadIds, setDownloadIds] = useState<number[]>([]);
|
||||||
|
const [changeStatusIds, setChangeStatusIds] = useState<number[]>([]);
|
||||||
|
|
||||||
|
const boxStatusOptions = useMemo(() => {
|
||||||
|
const p = ['READY_TO_INVOICE'] as BoxStatus[];
|
||||||
|
if (isAdmin) {
|
||||||
|
p.push('READY');
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}, [isAdmin]);
|
||||||
|
|
||||||
|
const getBoxesQuery = useRequest(
|
||||||
|
() =>
|
||||||
|
box_requests.getAll({
|
||||||
|
page: page,
|
||||||
|
cargoId: keyword,
|
||||||
|
status: boxStatusFilter,
|
||||||
|
direction: 'desc',
|
||||||
|
sort: 'id',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
dependencies: [page, boxStatusFilter],
|
||||||
|
selectData(data) {
|
||||||
|
return data.data.data;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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<IBox>[] = [
|
||||||
|
{
|
||||||
|
label: t('No'),
|
||||||
|
width: 100,
|
||||||
|
renderCell(data, rowIndex) {
|
||||||
|
return (page - 1) * pageSize + rowIndex + 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'id',
|
||||||
|
label: "Qo'shish",
|
||||||
|
width: 120,
|
||||||
|
renderCell: data => {
|
||||||
|
return <Button onClick={() => navigation.push(pageLinks.dashboard.boxes.edit(data.id))}>
|
||||||
|
<Add />
|
||||||
|
</Button>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'partyName',
|
||||||
|
label: t('party_name'),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'name',
|
||||||
|
label: t('name'),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'packetNetWeight',
|
||||||
|
label: t("weight"),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'totalItems',
|
||||||
|
label: t('count_of_items'),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'totalNetWeight',
|
||||||
|
label: t("party_weight"),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'cargoId',
|
||||||
|
label: t('cargo_id'),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'passportName',
|
||||||
|
label: t('client'),
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataKey: 'status',
|
||||||
|
label: t('status'),
|
||||||
|
width: 240,
|
||||||
|
renderHeaderCell(rowIndex) {
|
||||||
|
return (
|
||||||
|
<Stack direction={'row'} alignItems={'center'}>
|
||||||
|
<span>{t('box_status')}</span>
|
||||||
|
<ActionPopMenu
|
||||||
|
buttons={BoxStatusList.map(stat => {
|
||||||
|
return {
|
||||||
|
icon: <Circle sx={{ path: { color: getStatusColor(stat) } }} />,
|
||||||
|
label: t(stat),
|
||||||
|
onClick() {
|
||||||
|
setBoxStatusFilter(stat);
|
||||||
|
setPage(1);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
mainIcon={<FilterList />}
|
||||||
|
placement={{
|
||||||
|
anchorOrigin: {
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'center',
|
||||||
|
},
|
||||||
|
transformOrigin: {
|
||||||
|
horizontal: 'center',
|
||||||
|
vertical: 'top',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
renderCell(data) {
|
||||||
|
return (
|
||||||
|
<StatusChangePopup
|
||||||
|
anchor={{
|
||||||
|
status: data.status,
|
||||||
|
text: t(data.status),
|
||||||
|
}}
|
||||||
|
loading={changeStatusIds.includes(data.id)}
|
||||||
|
buttons={boxStatusOptions.map(stat => {
|
||||||
|
return {
|
||||||
|
icon: (
|
||||||
|
<Circle
|
||||||
|
sx={{
|
||||||
|
path: {
|
||||||
|
color: getStatusColor(stat),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
label: t(stat),
|
||||||
|
onClick: () => {
|
||||||
|
onChangeStatus(data.id, stat);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
width: 100,
|
||||||
|
numeric: true,
|
||||||
|
renderCell(data, rowIndex) {
|
||||||
|
return (
|
||||||
|
<ActionPopMenu
|
||||||
|
buttons={[
|
||||||
|
{
|
||||||
|
icon: <Edit sx={{ path: { color: '#3489E4' } }} />,
|
||||||
|
label: t('edit'),
|
||||||
|
onClick: () => {
|
||||||
|
navigation.push(pageLinks.dashboard.boxes.edit(data.id));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <Delete sx={{ path: { color: '#3489E4' } }} />,
|
||||||
|
label: t('delete'),
|
||||||
|
onClick: () => {
|
||||||
|
onDelete(data.id);
|
||||||
|
},
|
||||||
|
dontCloseOnClick: true,
|
||||||
|
loading: deleteIds.includes(data.id),
|
||||||
|
},
|
||||||
|
...(data.status === 'READY'
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
icon: <Download sx={{ path: { color: '#3489E4' } }} />,
|
||||||
|
label: t('download_excel'),
|
||||||
|
onClick: () => {
|
||||||
|
onDownloadExcel(data.id);
|
||||||
|
},
|
||||||
|
loading: downloadIds.includes(data.id),
|
||||||
|
dontCloseOnClick: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack direction={'row'} mb={3} spacing={3}>
|
||||||
|
<BaseButton colorVariant='blue' startIcon={<Add />} href={pageLinks.dashboard.boxes.create}>
|
||||||
|
{t('create_packet')}
|
||||||
|
</BaseButton>
|
||||||
|
</Stack>
|
||||||
|
<Box
|
||||||
|
width={1}
|
||||||
|
mb={3}
|
||||||
|
sx={{
|
||||||
|
padding: '28px',
|
||||||
|
borderRadius: '16px',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack mb={3.5} direction={'row'} justifyContent={'space-between'} alignItems={'center'}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: '20px',
|
||||||
|
lineHeight: '24px',
|
||||||
|
fontWeight: 600,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
color: '#000',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('packet')}
|
||||||
|
</Typography>
|
||||||
|
<Stack direction={'row'} alignItems={'center'} spacing={2}>
|
||||||
|
<BaseInput
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: <Search color='primary' />,
|
||||||
|
}}
|
||||||
|
placeholder={t('filter_packet_name')}
|
||||||
|
value={keyword}
|
||||||
|
onChange={e => {
|
||||||
|
setKeyword(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BaseButton colorVariant='gray' startIcon={<FilterListOff />} size='small' onClick={resetFilter}>
|
||||||
|
{t('reset_filter')}
|
||||||
|
</BaseButton>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<Box mb={6}>
|
||||||
|
<MyTable columns={columns} data={list} loading={loading} />
|
||||||
|
</Box>
|
||||||
|
<Stack direction={'row'} justifyContent={'center'}>
|
||||||
|
<BasePagination page={page} pageSize={pageSize} totalCount={totalElements} onChange={handleChange} />
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardRealBoxesPage;
|
||||||
1
src/routes/private/real-boxes/index.ts
Normal file
1
src/routes/private/real-boxes/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './DashboardRealBoxesPage';
|
||||||
Reference in New Issue
Block a user