feat: add fetch for multi-stub (#59)
Some checks failed
it-academy/dry-wash-pl/pipeline/head There was a failure building this commit
it-academy/dry-wash-pl/pipeline/pr-main There was a failure building this commit

This commit is contained in:
Ильназ 2024-12-22 12:00:12 +03:00
parent fc699e7890
commit 6096cfc15c
10 changed files with 205 additions and 59 deletions

View File

@ -11,6 +11,7 @@
"dry-wash.arm.order.table.header.washingTime": "Время мойки", "dry-wash.arm.order.table.header.washingTime": "Время мойки",
"dry-wash.arm.order.table.header.orderDate": "Дата заказа", "dry-wash.arm.order.table.header.orderDate": "Дата заказа",
"dry-wash.arm.order.table.header.status": "Статус", "dry-wash.arm.order.table.header.status": "Статус",
"dry-wash.arm.order.table.header.masters": "Мастер",
"dry-wash.arm.order.table.header.telephone": "Телефон", "dry-wash.arm.order.table.header.telephone": "Телефон",
"dry-wash.arm.order.table.header.location": "Расположение", "dry-wash.arm.order.table.header.location": "Расположение",
"dry-wash.arm.order.table.empty": "Список пуст", "dry-wash.arm.order.table.empty": "Список пуст",
@ -89,4 +90,4 @@
"dry-wash.errorBoundary.description": "Мы уже работаем над исправлением проблемы", "dry-wash.errorBoundary.description": "Мы уже работаем над исправлением проблемы",
"dry-wash.errorBoundary.button.reload": "Перезагрузить страницу", "dry-wash.errorBoundary.button.reload": "Перезагрузить страницу",
"dry-wash.washTime.timeSlot": "{{start}} - {{end}}" "dry-wash.washTime.timeSlot": "{{start}} - {{end}}"
} }

View File

@ -34,7 +34,41 @@ const armService = () => {
return await response.json(); return await response.json();
}; };
return { fetchOrders, fetchMasters }; const addMaster = async ({
name,
phone,
}: {
name: string;
phone: string;
}) => {
const response = await fetch(`${endpoint}${ArmEndpoints.MASTERS}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, phone }),
});
if (!response.ok) {
throw new Error(`Failed to fetch masters: ${response.status}`);
}
return await response.json();
};
const deleteMaster = async ({ id }: { id: string }) => {
const response = await fetch(`${endpoint}${ArmEndpoints.MASTERS}/${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error(`Failed to fetch masters: ${response.status}`);
}
return await response.json();
};
return { fetchOrders, fetchMasters, addMaster, deleteMaster };
}; };
export { armService, ArmEndpoints }; export { armService, ArmEndpoints };

View File

@ -5,20 +5,53 @@ import {
MenuList, MenuList,
MenuItem, MenuItem,
IconButton, IconButton,
useToast,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { EditIcon } from '@chakra-ui/icons'; import { EditIcon } from '@chakra-ui/icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { armService } from '../../api/arm';
const MasterActionsMenu = () => { interface MasterActionsMenu {
id: string;
}
const MasterActionsMenu = ({ id }: MasterActionsMenu) => {
const { t } = useTranslation('~', { const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.master.table.actionsMenu', keyPrefix: 'dry-wash.arm.master.table.actionsMenu',
}); });
const { deleteMaster } = armService();
const toast = useToast();
const handleClickDelete = async () => {
try {
await deleteMaster({ id });
toast({
title: 'Мастер удалён.',
description: `Мастер с ID "${id}" успешно удалён.`,
status: 'success',
duration: 5000,
isClosable: true,
position: 'top-right',
});
} catch (error) {
toast({
title: 'Ошибка удаления мастера.',
description: 'Не удалось удалить мастера. Попробуйте ещё раз.',
status: 'error',
duration: 5000,
isClosable: true,
position: 'top-right',
});
console.error(error);
}
};
return ( return (
<Menu> <Menu>
<MenuButton icon={<EditIcon />} as={IconButton} variant='outline' /> <MenuButton icon={<EditIcon />} as={IconButton} variant='outline' />
<MenuList> <MenuList>
<MenuItem>{t('delete')}</MenuItem> <MenuItem onClick={handleClickDelete}>{t('delete')}</MenuItem>
</MenuList> </MenuList>
</Menu> </Menu>
); );

View File

@ -11,15 +11,47 @@ import {
DrawerFooter, DrawerFooter,
DrawerHeader, DrawerHeader,
DrawerOverlay, DrawerOverlay,
useToast,
InputGroup,
InputLeftElement,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { armService } from '../../api/arm';
import { PhoneIcon } from '@chakra-ui/icons';
const MasterDrawer = ({ isOpen, onClose }) => { const MasterDrawer = ({ isOpen, onClose }) => {
const { addMaster } = armService();
const toast = useToast();
const [newMaster, setNewMaster] = useState({ name: '', phone: '' }); const [newMaster, setNewMaster] = useState({ name: '', phone: '' });
const handleSave = () => { const handleSave = async () => {
console.log(`Сохранение мастера: ${newMaster}`); if (newMaster.name.trim() === '' || newMaster.phone.trim() === '') {
onClose(); return;
}
try {
await addMaster(newMaster);
toast({
title: 'Мастер создан.',
description: `Мастер "${newMaster.name}" успешно добавлен.`,
status: 'success',
duration: 5000,
isClosable: true,
position: 'top-right',
});
onClose();
} catch (error) {
toast({
title: 'Ошибка при создании мастера.',
description: 'Не удалось добавить мастера. Попробуйте еще раз.',
status: 'error',
duration: 5000,
isClosable: true,
position: 'top-right',
});
console.error(error);
}
}; };
const { t } = useTranslation('~', { const { t } = useTranslation('~', {
@ -36,6 +68,7 @@ const MasterDrawer = ({ isOpen, onClose }) => {
<FormControl mb='4'> <FormControl mb='4'>
<FormLabel>{t('inputName.label')}</FormLabel> <FormLabel>{t('inputName.label')}</FormLabel>
<Input <Input
// isInvalid
value={newMaster.name} value={newMaster.name}
onChange={(e) => onChange={(e) =>
setNewMaster({ ...newMaster, name: e.target.value }) setNewMaster({ ...newMaster, name: e.target.value })
@ -44,14 +77,21 @@ const MasterDrawer = ({ isOpen, onClose }) => {
/> />
</FormControl> </FormControl>
<FormControl> <FormControl>
<FormLabel> {t('inputPhone.label')}</FormLabel> <FormLabel>{t('inputPhone.label')}</FormLabel>
<Input
value={newMaster.phone} <InputGroup>
onChange={(e) => <InputLeftElement pointerEvents='none'>
setNewMaster({ ...newMaster, phone: e.target.value }) <PhoneIcon color='gray.300' />
} </InputLeftElement>
placeholder={t('inputPhone.placeholder')} <Input
/> // isInvalid
value={newMaster.phone}
onChange={(e) =>
setNewMaster({ ...newMaster, phone: e.target.value })
}
placeholder={t('inputPhone.placeholder')}
/>
</InputGroup>
</FormControl> </FormControl>
</DrawerBody> </DrawerBody>
<DrawerFooter> <DrawerFooter>

View File

@ -4,37 +4,30 @@ import { Badge, Link, Stack, Td, Tr } from '@chakra-ui/react';
import MasterActionsMenu from '../MasterActionsMenu'; import MasterActionsMenu from '../MasterActionsMenu';
import { getTimeSlot } from '../../lib/date-helpers'; import { getTimeSlot } from '../../lib/date-helpers';
export interface Schedule {
id: string;
startWashTime: string;
endWashTime: string;
}
export type MasterProps = { export type MasterProps = {
id: string; id: string;
name: string; name: string;
schedule: Schedule[];
phone: string; phone: string;
}; };
const MasterItem = ({ name, schedule, phone }) => { const MasterItem = ({ name, phone, id }) => {
return ( return (
<Tr> <Tr>
<Td>{name}</Td> <Td>{name}</Td>
<Td> <Td>
<Stack direction='row'> <Stack direction='row'>
{schedule.map(({ startWashTime, endWashTime }, index) => ( {/*{schedule.map(({ startWashTime, endWashTime }, index) => (*/}
<Badge colorScheme={'green'} key={index}> {/* <Badge colorScheme={'green'} key={index}>*/}
{getTimeSlot(startWashTime, endWashTime)} {/* {getTimeSlot(startWashTime, endWashTime)}*/}
</Badge> {/* </Badge>*/}
))} {/*))}*/}
</Stack> </Stack>
</Td> </Td>
<Td> <Td>
<Link href='tel:'>{phone}</Link> <Link href='tel:'>{phone}</Link>
</Td> </Td>
<Td> <Td>
<MasterActionsMenu /> <MasterActionsMenu id={id} />
</Td> </Td>
</Tr> </Tr>
); );

View File

@ -3,6 +3,7 @@ import { Td, Tr, Link, Select } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { MasterProps } from '../MasterItem/MasterItem';
import { getTimeSlot } from '../../lib'; import { getTimeSlot } from '../../lib';
const statuses = [ const statuses = [
@ -24,6 +25,9 @@ export type OrderProps = {
status?: GetArrItemType<typeof statuses>; status?: GetArrItemType<typeof statuses>;
phone?: string; phone?: string;
location?: string; location?: string;
master: MasterProps;
notes: '';
allMasters: MasterProps[];
}; };
type Status = (typeof statuses)[number]; type Status = (typeof statuses)[number];
@ -44,6 +48,8 @@ const OrderItem = ({
status, status,
phone, phone,
location, location,
master,
allMasters,
}: OrderProps) => { }: OrderProps) => {
const { t } = useTranslation('~', { const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.order', keyPrefix: 'dry-wash.arm.order',
@ -51,6 +57,7 @@ const OrderItem = ({
const [statusSelect, setStatus] = useState(status); const [statusSelect, setStatus] = useState(status);
const bgColor = statusColors[statusSelect]; const bgColor = statusColors[statusSelect];
const [masterSelect, setMaster] = useState(master?.name);
return ( return (
<Tr> <Tr>
@ -71,6 +78,19 @@ const OrderItem = ({
))} ))}
</Select> </Select>
</Td> </Td>
<Td>
<Select
value={masterSelect}
onChange={(e) => setMaster(e.target.value as OrderProps['status'])}
placeholder={t(`status.placeholder`)}
>
{allMasters.map((item) => (
<option key={item.id} value={item.name}>
{item.name}
</option>
))}
</Select>
</Td>
<Td> <Td>
<Link href='tel:'>{phone}</Link> <Link href='tel:'>{phone}</Link>
</Td> </Td>

View File

@ -19,12 +19,14 @@ import OrderItem from '../OrderItem';
import { OrderProps } from '../OrderItem/OrderItem'; import { OrderProps } from '../OrderItem/OrderItem';
import { armService } from '../../api/arm'; import { armService } from '../../api/arm';
import DateNavigator from '../DateNavigator'; import DateNavigator from '../DateNavigator';
import { MasterProps } from '../MasterItem/MasterItem';
const TABLE_HEADERS = [ const TABLE_HEADERS = [
'carNumber' as const, 'carNumber' as const,
'washingTime' as const, 'washingTime' as const,
'orderDate' as const, 'orderDate' as const,
'status' as const, 'status' as const,
'masters' as const,
'telephone' as const, 'telephone' as const,
'location' as const, 'location' as const,
]; ];
@ -35,21 +37,29 @@ const Orders = () => {
}); });
const { fetchOrders } = armService(); const { fetchOrders } = armService();
const { fetchMasters } = armService();
const toast = useToast(); const toast = useToast();
const [orders, setOrders] = useState<OrderProps[]>([]); const [orders, setOrders] = useState<OrderProps[]>([]);
const [allMasters, setAllMasters] = useState<MasterProps[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [currentDate, setCurrentDate] = useState(new Date()); const [currentDate, setCurrentDate] = useState(new Date());
useEffect(() => { useEffect(() => {
const loadOrders = async () => { const loadData = async () => {
setLoading(true); setLoading(true);
setError(null);
try { try {
const data = await fetchOrders({ date: currentDate }); const [ordersData, mastersData] = await Promise.all([
setOrders(data.body); fetchOrders({ date: currentDate }),
fetchMasters(),
]);
setOrders(ordersData.body);
setAllMasters(mastersData.body);
} catch (err) { } catch (err) {
setError(err.message); setError(err.message);
toast({ toast({
@ -64,8 +74,8 @@ const Orders = () => {
} }
}; };
loadOrders(); loadData();
}, [toast, t, currentDate]); }, [currentDate, toast, t]);
return ( return (
<Box p='8'> <Box p='8'>
@ -112,6 +122,7 @@ const Orders = () => {
!error && !error &&
orders.map((order, index) => ( orders.map((order, index) => (
<OrderItem <OrderItem
allMasters={allMasters}
key={index} key={index}
{...order} {...order}
status={order.status as OrderProps['status']} status={order.status as OrderProps['status']}

View File

@ -8,8 +8,8 @@ const commonError = { success: false, message: 'Что-то пошло не та
const sleep = const sleep =
(duration = 1000) => (duration = 1000) =>
(req, res, next) => (req, res, next) =>
setTimeout(next, duration); setTimeout(next, duration);
router.use(sleep()); router.use(sleep());
@ -23,6 +23,36 @@ router.get('/arm/masters', (req, res) => {
); );
}); });
router.post('/arm/masters', (req, res) => {
res
.status(/error/.test(STUBS.masters) ? 500 : 200)
.send(
/^error$/.test(STUBS.masters)
? commonError
: require(`../json/arm-masters/${STUBS.masters}.json`),
);
});
router.patch('/arm/masters/:id', (req, res) => {
res
.status(/error/.test(STUBS.masters) ? 500 : 200)
.send(
/^error$/.test(STUBS.masters)
? commonError
: require(`../json/arm-masters/${STUBS.masters}.json`),
);
});
router.delete('/arm/masters/:id', (req, res) => {
res
.status(/error/.test(STUBS.masters) ? 500 : 200)
.send(
/^error$/.test(STUBS.masters)
? commonError
: require(`../json/arm-masters/${STUBS.masters}.json`),
);
});
router.post('/arm/orders', (req, res) => { router.post('/arm/orders', (req, res) => {
res res
.status(/error/.test(STUBS.orders) ? 500 : 200) .status(/error/.test(STUBS.orders) ? 500 : 200)

View File

@ -4,31 +4,11 @@
{ {
"id": "masters1", "id": "masters1",
"name": "Иван Иванов", "name": "Иван Иванов",
"schedule": [ {
"id": "order1",
"startWashTime": "2024-11-24T10:30:00.000Z",
"endWashTime": "2024-11-24T16:30:00.000Z"
},
{
"id": "order2",
"startWashTime": "2024-11-24T11:30:00.000Z",
"endWashTime": "2024-11-24T17:30:00.000Z"
}],
"phone": "+7 900 123 45 67" "phone": "+7 900 123 45 67"
}, },
{ {
"id": "masters12", "id": "masters12",
"name": "Иван Иванов", "name": "Иван Иванов",
"schedule": [ {
"id": "order1",
"startWashTime": "2024-11-24T10:30:00.000Z",
"endWashTime": "2024-11-24T16:30:00.000Z"
},
{
"id": "order2",
"startWashTime": "2024-11-24T11:30:00.000Z",
"endWashTime": "2024-11-24T17:30:00.000Z"
}],
"phone": "+7 900 123 45 67" "phone": "+7 900 123 45 67"
} }
] ]

View File

@ -9,7 +9,9 @@
"orderDate": "2024-11-24T08:41:46.366Z", "orderDate": "2024-11-24T08:41:46.366Z",
"status": "progress", "status": "progress",
"phone": "79001234563", "phone": "79001234563",
"location": "Казань, ул. Баумана, 1" "location": "Казань, ул. Баумана, 1",
"master": [],
"notes": ""
}, },
{ {
"id": "order2", "id": "order2",
@ -19,7 +21,9 @@
"orderDate": "2024-11-24T07:40:46.366Z", "orderDate": "2024-11-24T07:40:46.366Z",
"status": "progress", "status": "progress",
"phone": "79001234567", "phone": "79001234567",
"location": "Казань, ул. Баумана, 43" "location": "Казань, ул. Баумана, 43",
"master": [],
"notes": ""
} }
] ]
} }