Compare commits

..

No commits in common. "1276b13fec3bc68ab2fb5d24d6d08c1914a503bc" and "2aa361e3dbaafb5b5de4bd25b23a402207b54021" have entirely different histories.

11 changed files with 32 additions and 209 deletions
locales
src
api
components
MasterActionsMenu
MasterDrawer
MasterItem
OrderItem
Orders
pages/arm
stubs
api
json/arm-orders

@ -62,7 +62,6 @@
"dry-wash.arm.order.table.header.washingTime": "Washing Time", "dry-wash.arm.order.table.header.washingTime": "Washing Time",
"dry-wash.arm.order.table.header.orderDate": "Order Date", "dry-wash.arm.order.table.header.orderDate": "Order Date",
"dry-wash.arm.order.table.header.status": "Status", "dry-wash.arm.order.table.header.status": "Status",
"dry-wash.arm.order.table.header.masters": "Master",
"dry-wash.arm.order.table.header.telephone": "Telephone", "dry-wash.arm.order.table.header.telephone": "Telephone",
"dry-wash.arm.order.table.header.location": "Location", "dry-wash.arm.order.table.header.location": "Location",
"dry-wash.arm.master.title": "Masters", "dry-wash.arm.master.title": "Masters",

@ -11,7 +11,6 @@
"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": "Список пуст",

@ -34,41 +34,7 @@ const armService = () => {
return await response.json(); return await response.json();
}; };
const addMaster = async ({ return { fetchOrders, fetchMasters };
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 };

@ -5,54 +5,20 @@ 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 onClick={handleClickDelete}>{t('delete')}</MenuItem> <MenuItem>{t('delete')}</MenuItem>
</MenuList> </MenuList>
</Menu> </Menu>
); );

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

@ -2,7 +2,7 @@ import React from 'react';
import { Badge, Link, Stack, Td, Tr } from '@chakra-ui/react'; import { Badge, Link, Stack, Td, Tr } from '@chakra-ui/react';
import MasterActionsMenu from '../MasterActionsMenu'; import MasterActionsMenu from '../MasterActionsMenu';
import { getTimeSlot } from '../../lib'; import { getTimeSlot } from '../../lib/date-helpers';
export interface Schedule { export interface Schedule {
id: string; id: string;
@ -13,11 +13,11 @@ export interface Schedule {
export type MasterProps = { export type MasterProps = {
id: string; id: string;
name: string; name: string;
phone: string;
schedule: Schedule[]; schedule: Schedule[];
phone: string;
}; };
const MasterItem = ({ name, phone, id, schedule }) => { const MasterItem = ({ name, schedule, phone }) => {
return ( return (
<Tr> <Tr>
<Td>{name}</Td> <Td>{name}</Td>
@ -34,7 +34,7 @@ const MasterItem = ({ name, phone, id, schedule }) => {
<Link href='tel:'>{phone}</Link> <Link href='tel:'>{phone}</Link>
</Td> </Td>
<Td> <Td>
<MasterActionsMenu id={id} /> <MasterActionsMenu />
</Td> </Td>
</Tr> </Tr>
); );

@ -3,7 +3,6 @@ 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 = [
@ -25,9 +24,6 @@ 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];
@ -48,8 +44,6 @@ 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',
@ -57,7 +51,6 @@ 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>
@ -78,19 +71,6 @@ 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>

@ -19,14 +19,12 @@ 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,
]; ];
@ -37,29 +35,21 @@ 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 loadData = async () => { const loadOrders = async () => {
setLoading(true); setLoading(true);
setError(null);
try { try {
const [ordersData, mastersData] = await Promise.all([ const data = await fetchOrders({ date: currentDate });
fetchOrders({ date: currentDate }), setOrders(data.body);
fetchMasters(),
]);
setOrders(ordersData.body);
setAllMasters(mastersData.body);
} catch (err) { } catch (err) {
setError(err.message); setError(err.message);
toast({ toast({
@ -74,8 +64,8 @@ const Orders = () => {
} }
}; };
loadData(); loadOrders();
}, [currentDate, toast, t]); }, [toast, t, currentDate]);
return ( return (
<Box p='8'> <Box p='8'>
@ -122,7 +112,6 @@ 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']}

@ -1,11 +1,12 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { AbsoluteCenter, Box, Spinner } from '@chakra-ui/react'; import { AbsoluteCenter, Spinner } from '@chakra-ui/react';
import LayoutArm from '../../components/LayoutArm'; import LayoutArm from '../../components/LayoutArm';
import authLogin from '../../keycloak'; import authLogin from '../../keycloak';
import { URLs } from '../../__data__/urls'; import { URLs } from '../../__data__/urls';
const Page = () => { const Page = () => {
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
@ -19,11 +20,9 @@ const Page = () => {
if (!user) if (!user)
return ( return (
<Box position='relative' height='100vh'>
<AbsoluteCenter> <AbsoluteCenter>
<Spinner /> <Spinner />
</AbsoluteCenter> </AbsoluteCenter>
</Box>
); );
return <LayoutArm />; return <LayoutArm />;

@ -23,36 +23,6 @@ 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)

@ -9,9 +9,7 @@
"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",
@ -21,9 +19,7 @@
"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": ""
} }
] ]
} }