Compare commits

...

9 Commits

Author SHA1 Message Date
9b0bda3cda fix: add schedule for masters
All checks were successful
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
it-academy/dry-wash-pl/pipeline/head This commit looks good
2025-01-12 00:34:56 +03:00
cad31605d9 fix: eslint fix 2025-01-12 00:34:37 +03:00
1dee68b65d fix: place the spinner in the center
Some checks failed
it-academy/dry-wash-pl/pipeline/pr-main There was a failure building this commit
2025-01-11 22:43:55 +03:00
6096cfc15c 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
2025-01-11 22:40:01 +03:00
fc699e7890 Merge pull request 'feat: Add a color scheme for the status of orders (#56)' (#57) from feat/colors-status into main
All checks were successful
it-academy/dry-wash-pl/pipeline/head This commit looks good
Reviewed-on: #57
Reviewed-by: Primakov Alexandr Alexandrovich <primakovpro@gmail.com>
2025-01-11 21:56:00 +03:00
7b24804498 feat: Add a color scheme for the status of orders (#56)
All checks were successful
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
it-academy/dry-wash-pl/pipeline/head This commit looks good
2024-12-29 22:02:55 +03:00
5498122109 Merge pull request 'feat: add order-view (#9)' (#51) from feature/order-view into main
Some checks are pending
it-academy/dry-wash-pl/pipeline/head Build queued...
Reviewed-on: #51
2024-12-22 19:25:59 +03:00
RustamRu
5e24537919 fix: import order-details (#9)
All checks were successful
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
2024-12-22 11:30:30 +03:00
RustamRu
8a0dff682b feat: add order-view (#9)
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
2024-12-22 11:15:50 +03:00
28 changed files with 604 additions and 54 deletions

View File

@@ -39,6 +39,15 @@
"dry-wash.order-create.car-body-select.options.sports-car" : "Sports-car",
"dry-wash.order-create.car-body-select.options.other": "Other",
"dry-wash.order-create.form.submit-button.label": "Submit",
"dry-wash.order-view.title": "Your order",
"dry-wash.order-view.error.title": "Error",
"dry-wash.order-view.fetch.error": "Failed to fetch the details of order #{{number}}",
"dry-wash.order-view.details.title": "Order #{{number}}",
"dry-wash.order-view.details.owner": "Owner",
"dry-wash.order-view.details.car": "Car",
"dry-wash.order-view.details.location": "Where",
"dry-wash.order-view.details.datetime-range": "When",
"dry-wash.order-view.details.alert": "The operator will contact you about the payment at the specified phone number",
"dry-wash.arm.master.add": "Add",
"dry-wash.arm.order.title": "Orders",
"dry-wash.arm.order.table.empty": "Table empty",
@@ -53,6 +62,7 @@
"dry-wash.arm.order.table.header.washingTime": "Washing Time",
"dry-wash.arm.order.table.header.orderDate": "Order Date",
"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.location": "Location",
"dry-wash.arm.master.title": "Masters",

View File

@@ -11,6 +11,7 @@
"dry-wash.arm.order.table.header.washingTime": "Время мойки",
"dry-wash.arm.order.table.header.orderDate": "Дата заказа",
"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.location": "Расположение",
"dry-wash.arm.order.table.empty": "Список пуст",
@@ -62,17 +63,26 @@
"dry-wash.order-create.form.washing-location-field.help": "Например, 55.754364, 48.743295 Университетская улица, 1, Иннополис, Верхнеуслонский район, Республика Татарстан (Татарстан), 420500",
"dry-wash.order-create.car-body-select.placeholder": "Не указан",
"dry-wash.order-create.car-body-select.options.sedan": "Седан",
"dry-wash.order-create.car-body-select.options.hatchback" : "Хэтчбек",
"dry-wash.order-create.car-body-select.options.crossover" : "Кроссовер",
"dry-wash.order-create.car-body-select.options.suv" : "Внедорожник",
"dry-wash.order-create.car-body-select.options.station-wagon" : "Универсал",
"dry-wash.order-create.car-body-select.options.coupe" : "Купе",
"dry-wash.order-create.car-body-select.options.minivan" : "Минивэн",
"dry-wash.order-create.car-body-select.options.pickup" : "Пикап",
"dry-wash.order-create.car-body-select.options.liftback" : "Лифтбек",
"dry-wash.order-create.car-body-select.options.sports-car" : "Спорткар",
"dry-wash.order-create.car-body-select.options.hatchback": "Хэтчбек",
"dry-wash.order-create.car-body-select.options.crossover": "Кроссовер",
"dry-wash.order-create.car-body-select.options.suv": "Внедорожник",
"dry-wash.order-create.car-body-select.options.station-wagon": "Универсал",
"dry-wash.order-create.car-body-select.options.coupe": "Купе",
"dry-wash.order-create.car-body-select.options.minivan": "Минивэн",
"dry-wash.order-create.car-body-select.options.pickup": "Пикап",
"dry-wash.order-create.car-body-select.options.liftback": "Лифтбек",
"dry-wash.order-create.car-body-select.options.sports-car": "Спорткар",
"dry-wash.order-create.car-body-select.options.other": "Другой",
"dry-wash.order-create.form.submit-button.label": "Отправить",
"dry-wash.order-view.title": "Ваш заказ",
"dry-wash.order-view.error.title": "Ошибка",
"dry-wash.order-view.fetch.error": "Не удалось загрузить детали заказа №{{number}}",
"dry-wash.order-view.details.title": "Заказ №{{number}}",
"dry-wash.order-view.details.owner": "Владелец",
"dry-wash.order-view.details.car": "Автомобиль",
"dry-wash.order-view.details.location": "Где",
"dry-wash.order-view.details.datetime-range": "Когда",
"dry-wash.order-view.details.alert": "С вами свяжется оператор насчет оплаты по указанному номеру телефона",
"dry-wash.notFound.title": "Страница не найдена",
"dry-wash.notFound.description": "К сожалению, запрашиваемая вами страница не существует.",
"dry-wash.notFound.button.back": "Вернуться на главную",
@@ -80,4 +90,4 @@
"dry-wash.errorBoundary.description": "Мы уже работаем над исправлением проблемы",
"dry-wash.errorBoundary.button.reload": "Перезагрузить страницу",
"dry-wash.washTime.timeSlot": "{{start}} - {{end}}"
}
}

View File

@@ -34,7 +34,41 @@ const armService = () => {
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 };

25
src/api/landing.ts Normal file
View File

@@ -0,0 +1,25 @@
import { getConfigValue } from '@brojs/cli';
import { Order } from '../models/landing';
enum LandingEndpoints {
ORDER_VIEW = '/order'
}
const LandingService = () => {
const endpoint = getConfigValue('dry-wash.api');
const fetchOrder = async (orderId: Order.Id) => {
const response = await fetch(`${endpoint}${LandingEndpoints.ORDER_VIEW}/${orderId}`);
if (!response.ok) {
throw new Error(`Failed to fetch order: ${response.status}`);
}
return await response.json();
};
return { fetchOrder };
};
export { LandingService, LandingEndpoints };

View File

@@ -5,20 +5,54 @@ import {
MenuList,
MenuItem,
IconButton,
useToast,
} from '@chakra-ui/react';
import { EditIcon } from '@chakra-ui/icons';
import { useTranslation } from 'react-i18next';
const MasterActionsMenu = () => {
import { armService } from '../../api/arm';
interface MasterActionsMenu {
id: string;
}
const MasterActionsMenu = ({ id }: MasterActionsMenu) => {
const { t } = useTranslation('~', {
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 (
<Menu>
<MenuButton icon={<EditIcon />} as={IconButton} variant='outline' />
<MenuList>
<MenuItem>{t('delete')}</MenuItem>
<MenuItem onClick={handleClickDelete}>{t('delete')}</MenuItem>
</MenuList>
</Menu>
);

View File

@@ -11,15 +11,48 @@ import {
DrawerFooter,
DrawerHeader,
DrawerOverlay,
useToast,
InputGroup,
InputLeftElement,
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { PhoneIcon } from '@chakra-ui/icons';
import { armService } from '../../api/arm';
const MasterDrawer = ({ isOpen, onClose }) => {
const { addMaster } = armService();
const toast = useToast();
const [newMaster, setNewMaster] = useState({ name: '', phone: '' });
const handleSave = () => {
console.log(`Сохранение мастера: ${newMaster}`);
onClose();
const handleSave = async () => {
if (newMaster.name.trim() === '' || newMaster.phone.trim() === '') {
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('~', {
@@ -36,6 +69,7 @@ const MasterDrawer = ({ isOpen, onClose }) => {
<FormControl mb='4'>
<FormLabel>{t('inputName.label')}</FormLabel>
<Input
// isInvalid
value={newMaster.name}
onChange={(e) =>
setNewMaster({ ...newMaster, name: e.target.value })
@@ -44,14 +78,21 @@ const MasterDrawer = ({ isOpen, onClose }) => {
/>
</FormControl>
<FormControl>
<FormLabel> {t('inputPhone.label')}</FormLabel>
<Input
value={newMaster.phone}
onChange={(e) =>
setNewMaster({ ...newMaster, phone: e.target.value })
}
placeholder={t('inputPhone.placeholder')}
/>
<FormLabel>{t('inputPhone.label')}</FormLabel>
<InputGroup>
<InputLeftElement pointerEvents='none'>
<PhoneIcon color='gray.300' />
</InputLeftElement>
<Input
// isInvalid
value={newMaster.phone}
onChange={(e) =>
setNewMaster({ ...newMaster, phone: e.target.value })
}
placeholder={t('inputPhone.placeholder')}
/>
</InputGroup>
</FormControl>
</DrawerBody>
<DrawerFooter>

View File

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

View File

@@ -3,7 +3,8 @@ import { Td, Tr, Link, Select } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs';
import { getTimeSlot } from '../../lib/date-helpers';
import { MasterProps } from '../MasterItem/MasterItem';
import { getTimeSlot } from '../../lib';
const statuses = [
'pending' as const,
@@ -24,6 +25,19 @@ export type OrderProps = {
status?: GetArrItemType<typeof statuses>;
phone?: string;
location?: string;
master: MasterProps;
notes: '';
allMasters: MasterProps[];
};
type Status = (typeof statuses)[number];
const statusColors: Record<Status, string> = {
pending: 'yellow.100',
progress: 'blue.100',
working: 'orange.100',
canceled: 'red.100',
complete: 'green.100',
};
const OrderItem = ({
@@ -34,12 +48,16 @@ const OrderItem = ({
status,
phone,
location,
master,
allMasters,
}: OrderProps) => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.order',
});
const [statusSelect, setStatus] = useState(status);
const bgColor = statusColors[statusSelect];
const [masterSelect, setMaster] = useState(master?.name);
return (
<Tr>
@@ -51,6 +69,7 @@ const OrderItem = ({
value={statusSelect}
onChange={(e) => setStatus(e.target.value as OrderProps['status'])}
placeholder={t(`status.placeholder`)}
bg={bgColor}
>
{statuses.map((status) => (
<option key={status} value={status}>
@@ -59,6 +78,19 @@ const OrderItem = ({
))}
</Select>
</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>
<Link href='tel:'>{phone}</Link>
</Td>

View File

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

View File

@@ -0,0 +1 @@
export { OrderDetails } from './order-details';

View File

@@ -0,0 +1,99 @@
import React, { FC } from 'react';
import {
Alert,
AlertIcon,
Heading,
HStack,
UnorderedList,
VStack,
ListItem,
Text,
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { Order } from '../../../models/landing';
import { formatDatetime } from '../../../lib';
import { carBodySelectOptions } from '../../order-form/form/car-body/helper';
import { OrderStatus } from './status';
type OrderDetailsProps = Order.View;
export const OrderDetails: FC<OrderDetailsProps> = ({
id,
status,
phone,
carNumber,
carBody,
carColor,
location,
datetimeBegin,
datetimeEnd,
}) => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.order-view.details',
});
const { t: tCarBody } = useTranslation('~', {
keyPrefix: 'dry-wash.order-create.car-body-select.options',
});
return (
<VStack p={4} alignItems='flex-start' gap={4}>
<HStack
width='full'
flexWrap='wrap'
justifyContent='space-between'
gap={2}
>
<Heading as='h2' size='lg'>
{t('title', { number: id })}
</Heading>
<OrderStatus value={status} />
</HStack>
<UnorderedList styleType='none'>
{[
{
label: t('owner'),
value: phone,
},
{
label: t('car'),
value: [
carNumber,
tCarBody(
`${carBodySelectOptions.find(({ value }) => value === carBody)?.labelTKey}`,
),
carColor,
]
.filter((v) => v)
.join(', '),
},
{
label: t('location'),
value: location,
},
{
label: t('datetime-range'),
value: [
formatDatetime(datetimeBegin),
formatDatetime(datetimeEnd),
].join(' - '),
},
].map(({ label, value }, i) => (
<ListItem key={i}>
{label}:{' '}
<Text as='span' color='primary.500' fontWeight='bold'>
{value}
</Text>
</ListItem>
))}
</UnorderedList>
<Alert status='info' alignItems='flex-start'>
<AlertIcon />
{t('alert')}
</Alert>
</VStack>
);
};
// todo: add i18n for date & time

View File

@@ -0,0 +1 @@
export { OrderStatus } from './order-status';

View File

@@ -0,0 +1,49 @@
import { Tag, TagProps } from '@chakra-ui/react';
import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { TFunction } from 'i18next';
import { Order } from '../../../../models/landing';
const getPropsByStatus = (
status: Order.Status,
t: TFunction<'~', 'dry-wash.arm.order.status'>,
): TagProps => {
switch (status) {
case 'canceled':
return {
colorScheme: 'red',
children: t('canceled'),
};
case 'progress':
return {
colorScheme: 'yellow',
children: t('progress'),
};
case 'pending':
return {
colorScheme: 'yellow',
children: t('pending'),
};
case 'working':
return {
colorScheme: 'yellow',
children: t('working'),
};
case 'complete':
return {
colorScheme: 'green',
children: t('complete'),
};
}
};
export const OrderStatus: FC<{ value: Order.Status }> = ({ value }) => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.order.status',
});
const props = getPropsByStatus(value, t);
return <Tag variant='solid' {...props} />;
};

View File

@@ -0,0 +1 @@
export * from './details';

View File

@@ -10,3 +10,7 @@ export const getTimeSlot = (start: string, end: string) => {
end: endDay,
});
};
export const formatDatetime = (datetime: string) => {
return dayjs(datetime).format('HH:mm DD.MM.YYYY');
};

View File

@@ -1 +1,2 @@
export * from './date-helpers';
export * from './types';

View File

@@ -1,2 +1 @@
export * as mockOrder from './order';
export * as mockReview from './review';

View File

@@ -1,7 +0,0 @@
import { Order } from "../../models";
export const orders: Order.View[] = [
{ id: 'id1' },
{ id: 'id2' },
{ id: 'id3' },
];

1
src/models/api/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './order-view';

View File

@@ -0,0 +1,14 @@
import { Order } from "../landing";
export type FetchOrderQueryResponse = {
id: string;
orderDate: string;
carNumber: string;
carBody: number;
carColor?: string;
startWashTime: string;
endWashTime: string;
status: Order.Status;
phone: string;
location: string;
};

View File

@@ -2,6 +2,12 @@ import { Car, Customer, Washing } from ".";
export type Id = string;
export type Status = 'pending' |
'progress' |
'working' |
'canceled' |
'complete';
export type Create = {
customer: {
phone: Customer.PhoneNumber,
@@ -20,4 +26,13 @@ export type Create = {
export type View = {
id: Id;
orderDate: string,
status: Status,
phone: Customer.PhoneNumber;
carNumber: Car.RegistrationNumber;
carBody: Car.BodyStyle;
carColor?: Car.Color;
location: Washing.Location;
datetimeBegin: Washing.AvailableBeginDateTime;
datetimeEnd: Washing.AvailableEndDateTime;
};

View File

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

View File

@@ -0,0 +1,40 @@
import { useEffect, useState } from 'react';
import { LandingService } from '../../api/landing';
import { Order } from '../../models/landing';
import { FetchOrderQueryResponse } from '../../models/api';
export const useFetchOrderDetails = ({
orderId,
}: {
orderId: Order.View['id'];
}) => {
const { fetchOrder } = LandingService();
const [data, setData] = useState<FetchOrderQueryResponse>();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const data = await fetchOrder(orderId);
setData(data.body);
} catch (error) {
setError(error.message);
} finally {
setIsLoading(false);
}
};
fetchData();
}, []);
return {
isLoading,
data,
error,
};
};

View File

@@ -1,7 +1,75 @@
import React from "react";
import React, { FC, useEffect } from 'react';
import { HStack, Spinner, useToast } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { Container, Heading, VStack } from '@chakra-ui/react';
import { useParams } from 'react-router-dom';
const Page = () => {
return <h1>Order view</h1>;
import { LandingThemeProvider } from '../../containers';
import { OrderDetails } from '../../components/order-view';
import { useFetchOrderDetails } from './helper';
const Page: FC = () => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.order-view',
});
const { orderId } = useParams();
const { isLoading, data, error } = useFetchOrderDetails({ orderId });
const toast = useToast();
useEffect(() => {
if (error) {
toast({
title: t('error.title'),
description: t('fetch.error'),
status: 'error',
duration: 5000,
isClosable: true,
position: 'bottom-right',
});
}
}, [error]);
return (
<LandingThemeProvider>
<Container
w='full'
maxWidth='container.xl'
minH='100vh'
padding={0}
bg='white'
centerContent
>
<VStack w='full' h='full' alignItems='stretch' flexGrow={1}>
<Heading textAlign='center' mt={4}>
{t('title')}
</Heading>
{isLoading ? (
<HStack width='full' justifyContent='center'>
<Spinner size='lg' />
</HStack>
) : (
data && (
<OrderDetails
id={data.id}
orderDate={data.orderDate}
status={data.status}
phone={data.phone}
carNumber={data.carNumber}
carBody={data.carBody}
carColor={data.carColor}
location={data.location}
datetimeBegin={data.startWashTime}
datetimeEnd={data.endWashTime}
/>
)
)}
</VStack>
</Container>
</LandingThemeProvider>
);
};
export default Page;

View File

@@ -2,7 +2,7 @@
/* eslint-disable @typescript-eslint/no-require-imports */
const router = require('express').Router();
const STUBS = { masters: 'success', orders: 'success' };
const STUBS = { masters: 'success', orders: 'success', orderView: 'success' };
router.get('/set/:name/:value', (req, res) => {
const { name, value } = req.params;
@@ -24,6 +24,11 @@ router.get('/', (req, res) => {
${generateRadioInput('orders', 'success')}
${generateRadioInput('orders', 'error')}
</fieldset>
<fieldset>
<legend>Лендинг - Детали заказа</legend>
${generateRadioInput('orderView', 'success')}
${generateRadioInput('orderView', 'error')}
</fieldset>
</div>`);
});

View File

@@ -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) => {
res
.status(/error/.test(STUBS.orders) ? 500 : 200)
@@ -33,6 +63,19 @@ router.post('/arm/orders', (req, res) => {
);
});
router.get('/order/:orderId', ({ params }, res) => {
const { orderId } = params;
const stubName = `${orderId}-${STUBS.orderView}`;
res
.status(/error/.test(stubName) ? 500 : 200)
.send(
/^error$/.test(stubName)
? commonError
: require(`../json/landing-order-view/${stubName}.json`),
);
});
router.use('/admin', require('./admin'));
module.exports = router;

View File

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

View File

@@ -0,0 +1,14 @@
{
"success": true,
"body": {
"id": "order1",
"orderDate": "2024-11-24T08:41:46.366Z",
"status": "progress",
"carNumber": "A123BC",
"carBody": 1,
"startWashTime": "2024-11-24T10:30:00.000Z",
"endWashTime": "2024-11-24T16:30:00.000Z",
"phone": "79001234563",
"location": "55.754364, 48.743295 Университетская улица, 1, Иннополис, Верхнеуслонский район, Республика Татарстан (Татарстан), 420500"
}
}