434 lines
16 KiB
TypeScript
434 lines
16 KiB
TypeScript
'use client';
|
|
|
|
import ActionPopMenu from '@/components/common/ActionPopMenu';
|
|
import { ColumnData, MyTable } from '@/components/common/MyTable';
|
|
import BaseButton from '@/components/ui-kit/BaseButton';
|
|
import BaseInput from '@/components/ui-kit/BaseInput';
|
|
import BasePagination from '@/components/ui-kit/BasePagination';
|
|
import { selectDefaultStyles } from '@/components/ui-kit/BaseReactSelect';
|
|
import { useAuthContext } from '@/context/auth-context';
|
|
import { BoxStatus, BoxStatusList } from '@/data/box/box.model';
|
|
import { box_requests } from '@/data/box/box.requests';
|
|
import { Product } from '@/data/item/item.mode';
|
|
import { item_requests } from '@/data/item/item.requests';
|
|
import { party_requests } from '@/data/party/party.requests';
|
|
import { UserRoleEnum } from '@/data/user/user.model';
|
|
import { DEFAULT_PAGE_SIZE } from '@/helpers/constants';
|
|
import useInput from '@/hooks/useInput';
|
|
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
|
import useRequest from '@/hooks/useRequest';
|
|
import AddPhotosModal from '@/routes/private/items/components/AddPhotosModal';
|
|
import EditItemModal from '@/routes/private/items/components/EditItemModal';
|
|
import { notifyUnknownError } from '@/services/notification';
|
|
import { getBoxStatusStyles, getStatusColor } from '@/theme/getStatusBoxStyles';
|
|
import { Add, Check, Circle, Delete, Edit, FilterList, FilterListOff, Search } from '@mui/icons-material';
|
|
import { Box, FormControl, MenuItem, Select, Stack, Typography } from '@mui/material';
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
import AsyncSelect from 'react-select/async';
|
|
|
|
type Props = {};
|
|
|
|
const DashboardGoodsPage = (props: Props) => {
|
|
const t = useMyTranslation();
|
|
const { user } = useAuthContext();
|
|
const [page, setPage] = useState(1);
|
|
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
|
const [modal, setModal] = useState<null | 'add_photo_item' | 'edit_item'>(null);
|
|
const [selectedItem, setSelectedItem] = useState<Product | null>(null);
|
|
const { value: keyword, onChange: handleKeyword, setValue: setKeyword } = useInput('');
|
|
const { value: trackKeyword, onChange: handleTrackKeyword, setValue: setTrackKeyword } = useInput('');
|
|
const [partyFilter, setPartyFilter] = useState<{ label: string; value: number } | undefined>(undefined);
|
|
const [boxFilter, setBoxFilter] = useState<{ label: string; value: number } | undefined>(undefined);
|
|
const [boxStatusFilter, setBoxStatusFilter] = useState<BoxStatus | undefined>(undefined);
|
|
const [cargoType, setCargoType] = useState<'AUTO' | 'AVIA'>(user?.role === 'ADMIN' ? 'AVIA' : user?.cargoType || 'AUTO');
|
|
|
|
const [deleteIds, setDeleteIds] = useState<number[]>([]);
|
|
|
|
const getItemsQuery = useRequest(
|
|
() =>
|
|
item_requests.getAll({
|
|
name: keyword,
|
|
page: page,
|
|
status: boxStatusFilter,
|
|
packetId: boxFilter?.value,
|
|
partyId: partyFilter?.value,
|
|
trekId: trackKeyword,
|
|
direction: 'desc',
|
|
cargoType,
|
|
sort: 'id',
|
|
}),
|
|
{
|
|
selectData(data) {
|
|
return data.data.data;
|
|
},
|
|
dependencies: [page, boxStatusFilter, boxFilter, partyFilter, cargoType],
|
|
}
|
|
);
|
|
|
|
const {
|
|
data: list,
|
|
totalElements,
|
|
totalPages,
|
|
} = useMemo(() => {
|
|
if (getItemsQuery.data?.data) {
|
|
return {
|
|
data: getItemsQuery.data.data,
|
|
totalElements: getItemsQuery.data.totalElements,
|
|
totalPages: getItemsQuery.data.totalPages,
|
|
};
|
|
} else {
|
|
return {
|
|
data: [],
|
|
totalElements: 0,
|
|
totalPages: 0,
|
|
};
|
|
}
|
|
}, [getItemsQuery]);
|
|
const loading = getItemsQuery.loading;
|
|
|
|
const handleChange = (newPage: number) => {
|
|
setPage(newPage);
|
|
};
|
|
|
|
const resetFilter = () => {
|
|
setPage(1);
|
|
setKeyword('');
|
|
setTrackKeyword('');
|
|
setBoxStatusFilter(undefined);
|
|
// @ts-expect-error
|
|
setPartyFilter(null);
|
|
// @ts-expect-error
|
|
setBoxFilter(null);
|
|
};
|
|
|
|
const closeModal = () => {
|
|
setModal(null);
|
|
setSelectedItem(null);
|
|
};
|
|
|
|
const openEditModal = (item: Product) => {
|
|
setModal('edit_item');
|
|
setSelectedItem(item);
|
|
};
|
|
|
|
const openAddPhotoModal = (item: Product) => {
|
|
setModal('add_photo_item');
|
|
setSelectedItem(item);
|
|
};
|
|
|
|
const onSuccessEdit = () => {
|
|
getItemsQuery.refetch();
|
|
closeModal();
|
|
};
|
|
|
|
const onDelete = async (id: number) => {
|
|
if (deleteIds.includes(id)) return;
|
|
|
|
try {
|
|
setDeleteIds(p => [...p, id]);
|
|
await item_requests.delete({ itemId: id });
|
|
getItemsQuery.refetch();
|
|
} catch (error) {
|
|
notifyUnknownError(error);
|
|
} finally {
|
|
setDeleteIds(prev => prev.filter(i => i !== id));
|
|
}
|
|
};
|
|
|
|
const { data: defaultPartyOptions } = useRequest(() => party_requests.getAll({ cargoType }), {
|
|
enabled: true,
|
|
selectData(data) {
|
|
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
|
|
},
|
|
dependencies: [cargoType],
|
|
placeholderData: [],
|
|
});
|
|
|
|
const { data: defaultBoxOptions } = useRequest(
|
|
() =>
|
|
box_requests.getAll({
|
|
partyId: partyFilter?.value,
|
|
cargoType,
|
|
}),
|
|
{
|
|
enabled: !!partyFilter,
|
|
selectData(data) {
|
|
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
|
|
},
|
|
placeholderData: [],
|
|
dependencies: [partyFilter],
|
|
}
|
|
);
|
|
|
|
const partyOptions = (inputValue: string) => {
|
|
return party_requests.getAll({ partyName: inputValue }).then(res => {
|
|
return res.data.data.data.map(p => ({ label: p.name, value: p.id }));
|
|
});
|
|
};
|
|
|
|
const boxOptions = (inputValue: string) => {
|
|
return box_requests.getAll({ cargoId: inputValue }).then(res => {
|
|
return res.data.data.data.map(p => ({ label: p.name, value: p.id }));
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
const timeoutId = setTimeout(() => {
|
|
setPage(1);
|
|
getItemsQuery.refetch();
|
|
}, 350);
|
|
return () => clearTimeout(timeoutId);
|
|
}, [keyword, trackKeyword]);
|
|
|
|
const columns: ColumnData<Product>[] = [
|
|
{
|
|
label: t('No'),
|
|
width: 100,
|
|
renderCell(data, rowIndex) {
|
|
return (page - 1) * pageSize + rowIndex + 1;
|
|
},
|
|
},
|
|
{
|
|
dataKey: 'id',
|
|
label: t('id'),
|
|
width: 100,
|
|
},
|
|
{
|
|
dataKey: 'name',
|
|
label: t('product_name'),
|
|
width: 300,
|
|
},
|
|
{
|
|
dataKey: 'trekId',
|
|
label: t('track_id'),
|
|
width: 300,
|
|
},
|
|
{
|
|
dataKey: 'boxName',
|
|
label: t('box_number'),
|
|
width: 300,
|
|
},
|
|
{
|
|
dataKey: 'partyName',
|
|
label: t('party'),
|
|
width: 300,
|
|
},
|
|
{
|
|
dataKey: 'weight',
|
|
label: t('weight'),
|
|
width: 300,
|
|
},
|
|
{
|
|
dataKey: 'amount',
|
|
label: t('quantity'),
|
|
width: 300,
|
|
},
|
|
{
|
|
dataKey: 'cargoId',
|
|
label: t('cargo_id'),
|
|
width: 300,
|
|
},
|
|
{
|
|
dataKey: 'price',
|
|
label: t('price'),
|
|
width: 150,
|
|
},
|
|
{
|
|
dataKey: 'totalPrice',
|
|
label: t('total_price'),
|
|
width: 150,
|
|
},
|
|
{
|
|
dataKey: 'status',
|
|
label: t('box_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 <Box sx={{ ...getBoxStatusStyles(data.status) }}>{t(data.status)}</Box>;
|
|
},
|
|
},
|
|
{
|
|
label: '',
|
|
width: 50,
|
|
numeric: true,
|
|
renderCell(data, rowIndex) {
|
|
const isUzbekUser = user?.role === UserRoleEnum.UZB_WORKER;
|
|
|
|
if (isUzbekUser && data.hasImage) {
|
|
return <Check />;
|
|
} else if (isUzbekUser && !data.hasImage) {
|
|
return (
|
|
<ActionPopMenu
|
|
buttons={[
|
|
{
|
|
icon: <Add sx={{ path: { color: '#3489E4' } }} />,
|
|
label: t('add_photo_to_item'),
|
|
onClick: () => {
|
|
openAddPhotoModal(data);
|
|
},
|
|
},
|
|
]}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ActionPopMenu
|
|
buttons={[
|
|
{
|
|
icon: <Edit sx={{ path: { color: '#3489E4' } }} />,
|
|
label: t('edit'),
|
|
onClick: () => {
|
|
openEditModal(data);
|
|
},
|
|
},
|
|
{
|
|
icon: <Delete sx={{ path: { color: '#3489E4' } }} />,
|
|
label: t('delete'),
|
|
onClick: () => {
|
|
onDelete(data.id);
|
|
},
|
|
dontCloseOnClick: true,
|
|
loading: deleteIds.includes(data.id),
|
|
},
|
|
]}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
|
|
return (
|
|
<Box>
|
|
<Box
|
|
width={1}
|
|
mb={3}
|
|
sx={{
|
|
padding: '28px',
|
|
borderRadius: '16px',
|
|
backgroundColor: '#fff',
|
|
}}
|
|
>
|
|
<Stack mb={3.5} direction={'row'} justifyContent={'space-between'} alignItems={'flex-start'}>
|
|
<Typography
|
|
sx={{
|
|
fontSize: '20px',
|
|
lineHeight: '24px',
|
|
fontWeight: 600,
|
|
textTransform: 'capitalize',
|
|
color: '#000',
|
|
}}
|
|
>
|
|
{t('products')}
|
|
</Typography>
|
|
<Stack direction={'row'} justifyContent={'flex-end'} alignItems={'center'} gap={2} flexWrap={'wrap'}>
|
|
<BaseInput
|
|
InputProps={{
|
|
startAdornment: <Search color='primary' />,
|
|
}}
|
|
value={keyword}
|
|
onChange={handleKeyword}
|
|
placeholder={t('filter_item_name')}
|
|
/>
|
|
|
|
<AsyncSelect
|
|
isClearable
|
|
value={partyFilter}
|
|
onChange={(newValue: any) => {
|
|
setPartyFilter(newValue);
|
|
setPage(1);
|
|
}}
|
|
styles={selectDefaultStyles}
|
|
noOptionsMessage={() => t('not_found')}
|
|
loadingMessage={() => t('loading')}
|
|
defaultOptions={defaultPartyOptions!}
|
|
loadOptions={partyOptions}
|
|
placeholder={t('filter_party_name')}
|
|
/>
|
|
|
|
<AsyncSelect
|
|
isClearable
|
|
value={boxFilter}
|
|
onChange={(newValue: any) => {
|
|
setBoxFilter(newValue);
|
|
setPage(1);
|
|
}}
|
|
styles={selectDefaultStyles}
|
|
noOptionsMessage={() => t('enter_box_name_to_find')}
|
|
loadingMessage={() => t('loading')}
|
|
defaultOptions={defaultBoxOptions!}
|
|
loadOptions={boxOptions}
|
|
placeholder={t('filter_box_name')}
|
|
/>
|
|
|
|
<BaseInput value={trackKeyword} onChange={handleTrackKeyword} placeholder={t('filter_track_id')} />
|
|
{user?.role === 'ADMIN' && (
|
|
<FormControl size='small' sx={{ minWidth: 200 }}>
|
|
<Select
|
|
value={cargoType ?? ''}
|
|
onChange={e => {
|
|
const value = e.target.value || undefined;
|
|
setCargoType(value as 'AVIA' | 'AUTO');
|
|
setPage(1);
|
|
}}
|
|
displayEmpty
|
|
>
|
|
<MenuItem value='AVIA'>AVIA</MenuItem>
|
|
<MenuItem value='AUTO'>AUTO</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
)}
|
|
<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>
|
|
|
|
{modal === 'edit_item' && !!selectedItem && (
|
|
<EditItemModal item={selectedItem} onClose={closeModal} onSuccess={onSuccessEdit} open />
|
|
)}
|
|
{modal === 'add_photo_item' && !!selectedItem && (
|
|
<AddPhotosModal item={selectedItem} onClose={closeModal} onSuccess={onSuccessEdit} open />
|
|
)}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default DashboardGoodsPage;
|