@@ -211,5 +211,6 @@
|
||||
"party_weight": "大量重量",
|
||||
"qr_code": "二维码",
|
||||
"created_at": "添加日期",
|
||||
"download_all_items_exel": "将所有产品上传至Excel"
|
||||
"download_all_items_exel": "将所有产品上传至Excel",
|
||||
"select_all": "全选,"
|
||||
}
|
||||
@@ -210,6 +210,7 @@
|
||||
"update_packet": "Edit Paket",
|
||||
"party_weight": "Batch weight",
|
||||
"download_all_items_exel": "Upload all products in Excel",
|
||||
"select_all": "Select all",
|
||||
"qr_code": "QR Code",
|
||||
"created_at": "Date of joining"
|
||||
}
|
||||
@@ -223,6 +223,7 @@
|
||||
"update_packet": "Редактировать пакет",
|
||||
"party_weight": "Вес партии",
|
||||
"download_all_items_exel": "Загрузить все товары в Excel",
|
||||
"select_all": "Выделить все",
|
||||
|
||||
"qr_code": "QR код",
|
||||
"created_at": "Дата добавления"
|
||||
|
||||
@@ -222,6 +222,7 @@
|
||||
"passport": "Pasport",
|
||||
"update_packet": "Paketni Tahrirlash",
|
||||
"party_weight": "Partiya og'irligi",
|
||||
"select_all": "Hammasini belgilash",
|
||||
|
||||
"qr_code": "QR kod",
|
||||
"created_at": "Qo‘shilgan sana"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { Divider } from '@mui/material';
|
||||
import Pagination from '@mui/material/Pagination';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { Divider } from '@mui/material';
|
||||
import * as React from 'react';
|
||||
|
||||
interface BasePaginationProps {
|
||||
page: number;
|
||||
@@ -23,9 +23,9 @@ export default function BasePagination({ page, pageSize, totalCount, onChange }:
|
||||
let newPage = inputValue;
|
||||
|
||||
if (inputValue === 0) {
|
||||
newPage = 1; // Agar 0 kiritsa, 1 page ga o‘tsin
|
||||
newPage = 1; // Agar 0 kiritsa, 1 page ga o‘tsin
|
||||
} else if (inputValue > maxPage) {
|
||||
newPage = maxPage; // Max page dan oshsa, max page ga o‘tsin
|
||||
newPage = maxPage; // Max page dan oshsa, max page ga o‘tsin
|
||||
}
|
||||
|
||||
if (newPage !== page) {
|
||||
@@ -37,14 +37,15 @@ export default function BasePagination({ page, pageSize, totalCount, onChange }:
|
||||
}, [inputValue, page, totalCount, pageSize, onChange]);
|
||||
|
||||
return (
|
||||
<Stack spacing={2} direction="row" divider={<Divider orientation="vertical" flexItem />}>
|
||||
<Stack spacing={2} direction='row' divider={<Divider orientation='vertical' flexItem />}>
|
||||
<Pagination
|
||||
page={page}
|
||||
size='large'
|
||||
count={Math.ceil(totalCount / pageSize)}
|
||||
onChange={(_, newPage) => onChange(newPage)}
|
||||
variant="outlined"
|
||||
shape="rounded"
|
||||
color="primary"
|
||||
variant='outlined'
|
||||
shape='rounded'
|
||||
color='primary'
|
||||
sx={{
|
||||
'.Mui-selected': {
|
||||
backgroundColor: theme => theme.palette.primary.main,
|
||||
@@ -54,9 +55,9 @@ export default function BasePagination({ page, pageSize, totalCount, onChange }:
|
||||
/>
|
||||
<input
|
||||
value={inputValue}
|
||||
type="number"
|
||||
style={{ width: "50px", textAlign: "center", outline: "none" }}
|
||||
onChange={(e) => setInputValue(Number(e.target.value))}
|
||||
type='number'
|
||||
style={{ width: '50px', textAlign: 'center', outline: 'none' }}
|
||||
onChange={e => setInputValue(Number(e.target.value))}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ export type Product = {
|
||||
cargoId: string;
|
||||
trekId: string;
|
||||
name: string;
|
||||
nameRu: string;
|
||||
amount: number;
|
||||
weight: number;
|
||||
price?: number;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BoxStatus } from '@/data/box/box.model';
|
||||
import { Product, CreateProductBodyType, UpdateProductBodyType } from '@/data/item/item.mode';
|
||||
import { CreateProductBodyType, Product, UpdateProductBodyType } from '@/data/item/item.mode';
|
||||
import { CommonResponseType, PageAble } from '@/helpers/types';
|
||||
import { request } from '@/services/request';
|
||||
|
||||
@@ -29,6 +29,9 @@ export const item_requests = {
|
||||
async find(params: { itemId?: number | string }) {
|
||||
return request.get<CommonResponseType<Product[]>>('/items/find', { params });
|
||||
},
|
||||
async list(params: { packetId?: number | string }) {
|
||||
return request.get<CommonResponseType<{ data: Product[] }>>('/items/list', { params });
|
||||
},
|
||||
async delete(params: { itemId: number | string }) {
|
||||
return request.delete<CommonResponseType>('/items/delete', { params });
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ export const BoxStatusList: BoxStatus[] = ['READY_TO_INVOICE', 'READY'];
|
||||
export interface IRealBox {
|
||||
boxName: string;
|
||||
id: number;
|
||||
packetsCount: number;
|
||||
itemCount: number;
|
||||
partyName: string;
|
||||
}
|
||||
|
||||
@@ -26,19 +26,22 @@ export interface IRealBoxDetail {
|
||||
|
||||
export interface RealCreateBoxBodyType {
|
||||
partyName: string;
|
||||
packetDtos: number[];
|
||||
packetItemDtos: { packetId: number; itemDtos: number[] }[];
|
||||
}
|
||||
|
||||
export interface UpdateRealBoxBodyType {
|
||||
boxId: string;
|
||||
partyName: string;
|
||||
packetDtos: number[];
|
||||
packetItemDtos: { packetId: number; itemDtos: number[] }[];
|
||||
}
|
||||
|
||||
export interface FormValues {
|
||||
partyName: string;
|
||||
paketIds: Array<{ id: string | number }>;
|
||||
id?: number;
|
||||
boxId?: string;
|
||||
partyName: string;
|
||||
partyId?: number;
|
||||
packetItemDtos: Array<{
|
||||
packetId: number;
|
||||
itemDtos: number[];
|
||||
}>;
|
||||
}
|
||||
@@ -2,37 +2,50 @@
|
||||
|
||||
import BaseButton from '@/components/ui-kit/BaseButton';
|
||||
import BaseIconButton from '@/components/ui-kit/BaseIconButton';
|
||||
import { party_requests } from '@/data/party/party.requests';
|
||||
import { useAuthContext } from '@/context/auth-context';
|
||||
import { box_requests } from '@/data/box/box.requests';
|
||||
import { item_requests } from '@/data/item/item.requests';
|
||||
import { party_requests } from '@/data/party/party.requests';
|
||||
import { FormValues, RealCreateBoxBodyType, UpdateRealBoxBodyType } from '@/data/real-box/real-box.model';
|
||||
import { real_box_requests } from '@/data/real-box/real-box.requests';
|
||||
import { pageLinks } from '@/helpers/constants';
|
||||
import { notifyUnknownError } from '@/services/notification';
|
||||
import { Box, FormHelperText, Grid, Stack, Typography, styled } from '@mui/material';
|
||||
import { AddCircleRounded, Close } from '@mui/icons-material';
|
||||
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';
|
||||
import { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect';
|
||||
import { useAuthContext } from '@/context/auth-context';
|
||||
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
||||
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||
import { FormValues, RealCreateBoxBodyType, UpdateRealBoxBodyType } from '@/data/real-box/real-box.model';
|
||||
import { notifyUnknownError } from '@/services/notification';
|
||||
import { AddCircleRounded, Close } from '@mui/icons-material';
|
||||
import {
|
||||
Box,
|
||||
Checkbox,
|
||||
Chip,
|
||||
CircularProgress,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
InputLabel,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
Select,
|
||||
Stack,
|
||||
styled,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import get from 'lodash.get';
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
import useInput from '@/hooks/useInput';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Controller, useFieldArray, useForm } from 'react-hook-form';
|
||||
|
||||
const StyledCreateBox = styled(Box)`
|
||||
.item-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
.item-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
& > * {
|
||||
flex: 1 1 1;
|
||||
}
|
||||
& > * {
|
||||
flex: 1 1 1;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
@@ -42,62 +55,55 @@ interface Props {
|
||||
boxId?: string;
|
||||
partyId?: number;
|
||||
partyName?: string;
|
||||
paketIds?: Array<{ id: number; packetName: string }>;
|
||||
paketIds?: Array<any>;
|
||||
};
|
||||
}
|
||||
|
||||
const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
const { user, isAdmin: isAdminUser } = useAuthContext();
|
||||
const { user } = useAuthContext();
|
||||
const editMode = !!initialValues?.id;
|
||||
const [keyword, handleKeyword] = useState('');
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const t = useMyTranslation();
|
||||
const params = useSearchParams();
|
||||
const { push } = useMyNavigation();
|
||||
const [partyId, setPartyId] = useState<number | string>('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const helperRef = useRef<{
|
||||
finished: boolean;
|
||||
partyFinished: boolean;
|
||||
clientFinished: boolean;
|
||||
settedDefaultParty: any;
|
||||
}>({
|
||||
settedDefaultParty: null,
|
||||
partyFinished: false,
|
||||
clientFinished: false,
|
||||
finished: false,
|
||||
});
|
||||
|
||||
console.log(keyword, "keyword");
|
||||
|
||||
console.log(initialValues);
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
reset,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm<FormValues>({
|
||||
defaultValues: {
|
||||
partyName: initialValues?.partyName || '',
|
||||
paketIds: initialValues?.paketIds
|
||||
? initialValues.paketIds.map((paket) => ({ id: paket.id }))
|
||||
packetItemDtos: initialValues?.paketIds
|
||||
? initialValues.paketIds.map(paket => ({
|
||||
packetId: paket.id,
|
||||
itemDtos: paket.items ? paket.items.map((item: any) => item.id) : [],
|
||||
}))
|
||||
: params.get('party_id')
|
||||
? [{ id: +params.get('party_id')! }]
|
||||
: [{ id: '' }],
|
||||
? [{ packetId: +params.get('party_id')!, itemDtos: [] }]
|
||||
: [{ packetId: 0, itemDtos: [] }],
|
||||
id: initialValues?.id,
|
||||
boxId: initialValues?.boxId,
|
||||
partyId: initialValues?.partyId,
|
||||
},
|
||||
});
|
||||
|
||||
// Reset form when initialValues change (for edit mode)
|
||||
useEffect(() => {
|
||||
if (initialValues) {
|
||||
reset({
|
||||
partyName: initialValues.partyName || '',
|
||||
paketIds: initialValues.paketIds
|
||||
? initialValues.paketIds.map((paket) => ({ id: paket.id }))
|
||||
: [{ id: '' }],
|
||||
packetItemDtos: initialValues.paketIds
|
||||
? initialValues.paketIds.map(paket => ({
|
||||
packetId: paket.id,
|
||||
itemDtos: paket.items ? paket.items.map((item: any) => item.id) : [],
|
||||
}))
|
||||
: [{ packetId: 0, itemDtos: [] }],
|
||||
id: initialValues.id,
|
||||
boxId: initialValues.boxId,
|
||||
partyId: initialValues.partyId,
|
||||
@@ -110,73 +116,56 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control,
|
||||
name: 'paketIds',
|
||||
name: 'packetItemDtos',
|
||||
keyName: 'key',
|
||||
});
|
||||
|
||||
const requiredText = t('required');
|
||||
|
||||
const getBoxesQuery = useRequest(
|
||||
() =>
|
||||
// Fetch parties data
|
||||
const { data: parties = [], isLoading: isLoadingParties } = useQuery({
|
||||
queryKey: ['parties-list', 'COLLECTING'],
|
||||
queryFn: () => party_requests.getAll({ status: 'COLLECTING' }),
|
||||
select: data =>
|
||||
data.data.data.data.map((p: any) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
})),
|
||||
});
|
||||
|
||||
// Fetch packets data
|
||||
const { data: packets = [], isLoading: isLoadingPackets } = useQuery({
|
||||
queryKey: ['packets-list', partyId, keyword],
|
||||
queryFn: () =>
|
||||
box_requests.getAll({
|
||||
partyId: partyId,
|
||||
cargoId: keyword
|
||||
cargoId: keyword,
|
||||
}),
|
||||
{
|
||||
selectData: (data) => data?.data?.data ?? [],
|
||||
enabled: !!partyId,
|
||||
},
|
||||
);
|
||||
select: data => data?.data?.data?.data.filter((box: any) => box.status === 'READY_TO_INVOICE') ?? [],
|
||||
enabled: !!partyId,
|
||||
});
|
||||
|
||||
const list = useMemo(() => {
|
||||
return getBoxesQuery.data?.data.filter((box: any) => box.status === 'READY_TO_INVOICE') ?? [];
|
||||
}, [getBoxesQuery.data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (partyId) {
|
||||
getBoxesQuery.refetch();
|
||||
}
|
||||
}, [partyId, getBoxesQuery.refetch]);
|
||||
|
||||
const { data: defaultParties } = useRequest(
|
||||
() => party_requests.getAll({ status: 'COLLECTING' }),
|
||||
{
|
||||
enabled: true,
|
||||
selectData: (data) =>
|
||||
data.data.data.data.map((p: any) => ({ value: p.id, label: p.name })),
|
||||
onSuccess: (data) => {
|
||||
if (!editMode && data?.data?.data?.data?.[0]) {
|
||||
const defaultParty = data.data.data.data[0];
|
||||
helperRef.current.settedDefaultParty = defaultParty;
|
||||
setValue('partyName', defaultParty.name);
|
||||
setValue('paketIds.0.id', defaultParty.id); // Use dot notation
|
||||
setPartyId(defaultParty.id);
|
||||
}
|
||||
helperRef.current.partyFinished = true;
|
||||
if (helperRef.current.clientFinished) {
|
||||
helperRef.current.finished = true;
|
||||
}
|
||||
},
|
||||
placeholderData: [],
|
||||
},
|
||||
);
|
||||
|
||||
const onSubmit = handleSubmit(async (values) => {
|
||||
const onSubmit = handleSubmit(async values => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const packetDtos = values.paketIds.map((paket) => Number(paket.id)).filter((id) => id);
|
||||
|
||||
if (editMode) {
|
||||
const updateBody: UpdateRealBoxBodyType = {
|
||||
boxId: initialValues!.boxId!,
|
||||
partyName: values.partyName,
|
||||
packetDtos,
|
||||
packetItemDtos: values.packetItemDtos.map(packet => ({
|
||||
packetId: packet.packetId,
|
||||
itemDtos: packet.itemDtos,
|
||||
})),
|
||||
};
|
||||
await real_box_requests.update(updateBody);
|
||||
} else {
|
||||
const createBody: RealCreateBoxBodyType = {
|
||||
partyName: values.partyName,
|
||||
packetDtos,
|
||||
packetItemDtos: values.packetItemDtos.map(packet => ({
|
||||
packetId: packet.packetId,
|
||||
itemDtos: packet.itemDtos,
|
||||
})),
|
||||
};
|
||||
await real_box_requests.create(createBody);
|
||||
}
|
||||
@@ -189,27 +178,183 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
}
|
||||
});
|
||||
|
||||
const partyOptions = async (inputValue: string) => {
|
||||
try {
|
||||
const res = await party_requests.getAll({
|
||||
status: 'COLLECTING',
|
||||
partyName: inputValue,
|
||||
});
|
||||
return res.data.data.data.map((p: any) => ({ label: p.name, value: p.id }));
|
||||
} catch (error) {
|
||||
notifyUnknownError(error);
|
||||
return [];
|
||||
const handlePartyChange = (event: any) => {
|
||||
const selectedParty = parties.find(p => p.id === event.target.value);
|
||||
if (selectedParty) {
|
||||
setValue('partyName', selectedParty.name);
|
||||
setPartyId(selectedParty.id);
|
||||
setKeyword('');
|
||||
setValue('packetItemDtos', [{ packetId: 0, itemDtos: [] }]);
|
||||
}
|
||||
};
|
||||
|
||||
const appendPaket = () => {
|
||||
append({ id: '' });
|
||||
const handlePacketChange = (index: number, value: number) => {
|
||||
setValue(`packetItemDtos.${index}.packetId`, value);
|
||||
setValue(`packetItemDtos.${index}.itemDtos`, []);
|
||||
};
|
||||
|
||||
const removePaket = (index: number) => {
|
||||
const appendPacket = () => {
|
||||
append({ packetId: 0, itemDtos: [] });
|
||||
};
|
||||
|
||||
const removePacket = (index: number) => {
|
||||
remove(index);
|
||||
};
|
||||
|
||||
// Component for each packet row to isolate product selection
|
||||
const PacketRow = ({ index, field }: { index: number; field: any }) => {
|
||||
const packetId = watch(`packetItemDtos.${index}.packetId`);
|
||||
|
||||
// Fetch products specific to this packet
|
||||
const { data: products, isLoading: isLoadingProducts } = useQuery({
|
||||
queryKey: ['product-list', packetId],
|
||||
queryFn: () => item_requests.list({ packetId }),
|
||||
select: data => data?.data?.data.data ?? [],
|
||||
enabled: !!packetId,
|
||||
});
|
||||
|
||||
return (
|
||||
<Box className='item-row' mb={2} display={'flex'} flexDirection={'column'}>
|
||||
<div style={{ width: '100%' }}>
|
||||
<Typography fontSize='18px' sx={{ textAlign: 'start' }} fontWeight={500} color='#5D5850'>
|
||||
{t('packet')}
|
||||
</Typography>
|
||||
</div>
|
||||
<Box
|
||||
className='item-row-field'
|
||||
sx={{ width: '100%' }}
|
||||
flex={1}
|
||||
display={'flex'}
|
||||
gap={2}
|
||||
justifyContent={'space-between'}
|
||||
alignItems={'center'}
|
||||
>
|
||||
<div style={{ width: '100%' }}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id={`packet-select-label-${index}`}>{t('packet')}</InputLabel>
|
||||
<Controller
|
||||
name={`packetItemDtos.${index}.packetId`}
|
||||
control={control}
|
||||
rules={{ required: requiredText }}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...field}
|
||||
labelId={`packet-select-label-${index}`}
|
||||
label={t('packet')}
|
||||
onChange={e => {
|
||||
field.onChange(e);
|
||||
handlePacketChange(index, e.target.value as number);
|
||||
}}
|
||||
value={field.value || ''}
|
||||
disabled={isLoadingPackets}
|
||||
>
|
||||
{isLoadingPackets ? (
|
||||
<MenuItem disabled>
|
||||
<CircularProgress size={24} />
|
||||
</MenuItem>
|
||||
) : (
|
||||
packets.map(packet => (
|
||||
<MenuItem key={packet.id} value={packet.id}>
|
||||
{packet.name || packet.name || `Box ${packet.id}`}
|
||||
</MenuItem>
|
||||
))
|
||||
)}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
{!!get(errors, `packetItemDtos.${index}.packetId`) && (
|
||||
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
<div>
|
||||
{fields.length > 1 && (
|
||||
<BaseIconButton
|
||||
size='small'
|
||||
colorVariant='icon-error'
|
||||
sx={{ flexShrink: 0, height: 'auto', marginTop: 1 }}
|
||||
onClick={() => removePacket(index)}
|
||||
>
|
||||
<Close />
|
||||
</BaseIconButton>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{packetId && (
|
||||
<Box mt={2} sx={{ width: '100%' }}>
|
||||
<Typography fontSize='18px' sx={{ textAlign: 'start' }} fontWeight={500} color='#5D5850' mb={2}>
|
||||
{t('products')}
|
||||
</Typography>
|
||||
{isLoadingProducts ? (
|
||||
<CircularProgress />
|
||||
) : (
|
||||
<FormControl fullWidth>
|
||||
<Box display='flex' alignItems='center' mb={1}>
|
||||
<Controller
|
||||
name={`packetItemDtos.${index}.itemDtos`}
|
||||
control={control}
|
||||
render={({ field: { onChange } }) => (
|
||||
<Checkbox
|
||||
checked={watch(`packetItemDtos.${index}.itemDtos`)?.length === products?.length}
|
||||
onChange={e => {
|
||||
if (e.target.checked) {
|
||||
setValue(`packetItemDtos.${index}.itemDtos`, products?.map(p => p.id) || []);
|
||||
} else {
|
||||
setValue(`packetItemDtos.${index}.itemDtos`, []);
|
||||
}
|
||||
}}
|
||||
indeterminate={
|
||||
watch(`packetItemDtos.${index}.itemDtos`)?.length > 0 &&
|
||||
watch(`packetItemDtos.${index}.itemDtos`)?.length < products?.length!
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Typography variant='body2'>{t('select_all')}</Typography>
|
||||
</Box>
|
||||
<Controller
|
||||
name={`packetItemDtos.${index}.itemDtos`}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...field}
|
||||
labelId={`product-select-label-${index}`}
|
||||
multiple
|
||||
value={field.value || []}
|
||||
onChange={e => field.onChange(e.target.value)}
|
||||
renderValue={selected => (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
{(selected as any[]).map(value => (
|
||||
<Chip
|
||||
key={value}
|
||||
label={products?.find(p => p.id === value)?.name}
|
||||
onDelete={() => {
|
||||
const newValue = (field.value || []).filter(id => id !== value);
|
||||
field.onChange(newValue);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
{products?.map(product => (
|
||||
<MenuItem key={product.id} value={product.id}>
|
||||
<Checkbox checked={(field.value || []).includes(product.id)} />
|
||||
<ListItemText primary={product.name} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledCreateBox
|
||||
width={1}
|
||||
@@ -220,53 +365,50 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
backgroundColor: '#fff',
|
||||
}}
|
||||
>
|
||||
<Box component="form" onSubmit={onSubmit}>
|
||||
<Typography variant="h5" mb={3.5}>
|
||||
<Box component='form' onSubmit={onSubmit}>
|
||||
<Typography variant='h5' mb={3.5}>
|
||||
{editMode ? t('update_box') : t('create_box')}
|
||||
</Typography>
|
||||
|
||||
<Grid container columnSpacing={2.5} rowSpacing={3} mb={3.5}>
|
||||
<Grid item xs={12}>
|
||||
<Typography fontSize="18px" fontWeight={500} color="#5D5850" mb={2}>
|
||||
<Typography fontSize='18px' fontWeight={500} color='#5D5850' mb={2}>
|
||||
{t('party_name')}
|
||||
</Typography>
|
||||
<Controller
|
||||
name="partyName"
|
||||
control={control}
|
||||
rules={{ required: requiredText }}
|
||||
render={({ field }) => (
|
||||
<AsyncSelect
|
||||
onChange={(newValue: any) => {
|
||||
field.onChange(newValue?.label ?? '');
|
||||
setPartyId(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')}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{!!errors.partyName && (
|
||||
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
|
||||
)}
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id='party-select-label'>{t('party_name')}</InputLabel>
|
||||
<Controller
|
||||
name='partyId'
|
||||
control={control}
|
||||
rules={{ required: requiredText }}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...field}
|
||||
labelId='party-select-label'
|
||||
label={t('party_name')}
|
||||
onChange={e => {
|
||||
field.onChange(e);
|
||||
handlePartyChange(e);
|
||||
}}
|
||||
value={field.value || ''}
|
||||
disabled={isLoadingParties}
|
||||
>
|
||||
{isLoadingParties ? (
|
||||
<MenuItem disabled>
|
||||
<CircularProgress size={24} />
|
||||
</MenuItem>
|
||||
) : (
|
||||
parties.map(party => (
|
||||
<MenuItem key={party.id} value={party.id}>
|
||||
{party.name}
|
||||
</MenuItem>
|
||||
))
|
||||
)}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
{!!errors.partyId && <FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>}
|
||||
</FormControl>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
@@ -276,95 +418,25 @@ const DashboardCreateRealBoxPage = ({ initialValues, partiesData }: Props) => {
|
||||
border: '2px solid #3489E4',
|
||||
background: '#FFF',
|
||||
padding: '24px',
|
||||
gap: '12px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<Typography fontSize="18px" fontWeight={500} color="#5D5850" mb={2}>
|
||||
{t('packet')}
|
||||
</Typography>
|
||||
{fields.map((field, index) => (
|
||||
<Box key={field.key} className="item-row" mb={2}>
|
||||
<Box className="item-row-field" flex={1}>
|
||||
<Controller
|
||||
name={`paketIds.${index}.id`}
|
||||
control={control}
|
||||
rules={{ required: requiredText }}
|
||||
render={({ field: paketField }) => (
|
||||
<AsyncSelect
|
||||
onChange={(newValue: any) => {
|
||||
paketField.onChange(newValue?.value ?? '');
|
||||
handleKeyword(newValue?.label ?? '');
|
||||
}}
|
||||
defaultValue={
|
||||
editMode && initialValues?.paketIds?.[index]
|
||||
? {
|
||||
value: initialValues.paketIds[index].id,
|
||||
label:
|
||||
initialValues.paketIds[index].packetName ||
|
||||
`Box ${initialValues.paketIds[index].id}`,
|
||||
}
|
||||
: null
|
||||
}
|
||||
styles={selectDefaultStyles}
|
||||
noOptionsMessage={() => t('not_found')}
|
||||
loadingMessage={() => t('loading')}
|
||||
onBlur={paketField.onBlur}
|
||||
name={paketField.name}
|
||||
defaultOptions={list.map((box: any) => ({
|
||||
value: box.id,
|
||||
label: box.box_name || box.name || `Box ${box.id}`,
|
||||
}))}
|
||||
loadOptions={async (inputValue: string) => {
|
||||
if (!partyId) return [];
|
||||
try {
|
||||
const res = await box_requests.getAll({
|
||||
partyId,
|
||||
cargoId: inputValue,
|
||||
});
|
||||
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')}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{!!get(errors, `paketIds.${index}.id`) && (
|
||||
<FormHelperText sx={{ color: 'red' }}>{requiredText}</FormHelperText>
|
||||
)}
|
||||
</Box>
|
||||
<BaseIconButton
|
||||
size="small"
|
||||
colorVariant="icon-error"
|
||||
sx={{ flexShrink: 0, height: 'auto', marginTop: 1 }}
|
||||
onClick={() => removePaket(index)}
|
||||
>
|
||||
<Close />
|
||||
</BaseIconButton>
|
||||
</Box>
|
||||
))}
|
||||
<BaseButton
|
||||
variant="outlined"
|
||||
onClick={appendPaket}
|
||||
startIcon={<AddCircleRounded />}
|
||||
>
|
||||
{t('add_packet')}
|
||||
</BaseButton>
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<PacketRow key={field.key} index={index} field={field} />
|
||||
))}
|
||||
<BaseButton variant='outlined' onClick={appendPacket} startIcon={<AddCircleRounded />} sx={{ mt: 2 }}>
|
||||
{t('add_packet')}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<BaseButton
|
||||
variant="contained"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
fullWidth
|
||||
>
|
||||
{editMode ? t('update_box') : t('create_box')}
|
||||
<BaseButton variant='contained' type='submit' disabled={loading} fullWidth sx={{ py: 1.5 }}>
|
||||
{loading ? <CircularProgress size={24} color='inherit' /> : editMode ? t('update_box') : t('create_box')}
|
||||
</BaseButton>
|
||||
</Box>
|
||||
</StyledCreateBox>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import Loader from '@/components/common/Loader';
|
||||
import { real_box_requests } from '@/data/real-box/real-box.requests';
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
import { useParams } from 'next/navigation';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import DashboardCreateRealBoxPage from './DashboardCreateRealBox';
|
||||
import { real_box_requests } from '@/data/real-box/real-box.requests';
|
||||
|
||||
type Props = {};
|
||||
|
||||
@@ -19,27 +19,28 @@ const DashboardEditRealBoxPage = (props: Props) => {
|
||||
},
|
||||
{
|
||||
selectData(data) {
|
||||
const boxData = data.data; // ⬅️ Faqat data.data, data.data.data emas
|
||||
const boxData = data.data;
|
||||
if (!boxData) return null;
|
||||
console.log(boxData.data, 'boxdata');
|
||||
|
||||
return {
|
||||
id: box_id,
|
||||
boxId: box_id!,
|
||||
partyId: boxData.data.party.id, // ⬅️ To‘g‘ri yo‘l
|
||||
partyId: boxData.data.party.id,
|
||||
partyName: boxData.data.party.name!,
|
||||
paketIds: boxData.data.packets!
|
||||
paketIds: boxData.data.packetItemDtos!,
|
||||
items: boxData.data.packetItemDtos.items,
|
||||
// id?: number;
|
||||
// boxId?: string;
|
||||
// partyId?: number;
|
||||
// partyName?: string;
|
||||
// paketIds?: Array<{ id: number; packetName: string }>;
|
||||
};
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
getOneBox.refetch()
|
||||
}, [box_id])
|
||||
|
||||
console.log(getOneBox);
|
||||
getOneBox.refetch();
|
||||
}, [box_id]);
|
||||
|
||||
if (getOneBox.loading || !getOneBox.data) {
|
||||
return <Loader p={8} size={96} />;
|
||||
|
||||
@@ -1,31 +1,25 @@
|
||||
'use client';
|
||||
|
||||
import ActionPopMenu from '@/components/common/ActionPopMenu';
|
||||
import { MyTable, ColumnData } from '@/components/common/MyTable';
|
||||
import StatusChangePopup from '@/components/common/StatusChangePopup';
|
||||
import { ColumnData, MyTable } from '@/components/common/MyTable';
|
||||
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 { BoxStatus } from '@/data/box/box.model';
|
||||
import { box_requests } from '@/data/box/box.requests';
|
||||
import { IRealBox } from '@/data/real-box/real-box.model';
|
||||
import { real_box_requests } from '@/data/real-box/real-box.requests';
|
||||
import { DEFAULT_PAGE_SIZE, pageLinks } from '@/helpers/constants';
|
||||
import 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, QrCode, 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 { log } from 'node:console';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Add, Delete, Download, Edit, FilterListOff, QrCode, Search } from '@mui/icons-material';
|
||||
import { Box, Button, Stack, Typography } from '@mui/material';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
type Props = {};
|
||||
|
||||
@@ -135,7 +129,7 @@ const DashboardRealBoxesPage = (props: Props) => {
|
||||
try {
|
||||
setDownloadIds(p => [...p, id]);
|
||||
const response = await real_box_requests.downloadQrCode({ boxId: id });
|
||||
console.log(response, "rres");
|
||||
console.log(response, 'rres');
|
||||
const file = new File([response.data], 'qr.png', { type: response.data.type });
|
||||
file_service.download(file);
|
||||
} catch (error) {
|
||||
@@ -178,25 +172,26 @@ const DashboardRealBoxesPage = (props: Props) => {
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("qr_code"),
|
||||
label: t('qr_code'),
|
||||
width: 120,
|
||||
renderCell: data => {
|
||||
return <Button
|
||||
onClick={() =>
|
||||
onDownloadQrCode(data.id)
|
||||
}>
|
||||
<QrCode />
|
||||
</Button >;
|
||||
return (
|
||||
<Button onClick={() => onDownloadQrCode(data.id)}>
|
||||
<QrCode />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
dataKey: 'id',
|
||||
label: t("add_more"),
|
||||
label: t('add_more'),
|
||||
width: 120,
|
||||
renderCell: data => {
|
||||
return <Button onClick={() => navigation.push(pageLinks.dashboard.real_boxes.edit(data.id))}>
|
||||
<Add />
|
||||
</Button>;
|
||||
return (
|
||||
<Button onClick={() => navigation.push(pageLinks.dashboard.real_boxes.edit(data.id))}>
|
||||
<Add />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -205,7 +200,7 @@ const DashboardRealBoxesPage = (props: Props) => {
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
dataKey: "boxName",
|
||||
dataKey: 'boxName',
|
||||
label: t('party_name'),
|
||||
width: 120,
|
||||
},
|
||||
@@ -220,7 +215,7 @@ const DashboardRealBoxesPage = (props: Props) => {
|
||||
// width: 120,
|
||||
// },
|
||||
{
|
||||
dataKey: 'packetsCount',
|
||||
dataKey: 'itemCount',
|
||||
label: t('count_of_items'),
|
||||
width: 120,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user