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

This commit is contained in:
Ильназ 2024-12-07 23:40:22 +03:00
parent 7c157574c8
commit 0b2dae79ac
8 changed files with 196 additions and 27 deletions

View File

@ -24,6 +24,6 @@ module.exports = {
}, },
}, },
config: { config: {
'dry-wash-pl.api': '/api', 'dry-wash.api': '/api',
}, },
}; };

View File

@ -16,6 +16,8 @@
"dry-wash.landing.social-proof-section.heading": "We are being chosen", "dry-wash.landing.social-proof-section.heading": "We are being chosen",
"dry-wash.arm.master.add": "Add", "dry-wash.arm.master.add": "Add",
"dry-wash.arm.order.title": "Orders", "dry-wash.arm.order.title": "Orders",
"dry-wash.arm.order.table.empty": "Table empty",
"dry-wash.arm.order.error.title": "Error loading data",
"dry-wash.arm.order.status.progress": "In Progress", "dry-wash.arm.order.status.progress": "In Progress",
"dry-wash.arm.order.status.complete": "Completed", "dry-wash.arm.order.status.complete": "Completed",
"dry-wash.arm.order.status.pending": "Pending", "dry-wash.arm.order.status.pending": "Pending",
@ -30,6 +32,8 @@
"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",
"dry-wash.arm.master.table.header.name": "Name", "dry-wash.arm.master.table.header.name": "Name",
"dry-wash.arm.master.table.empty": "Table empty",
"dry-wash.arm.master.error.title": "Error loading data",
"dry-wash.arm.master.table.header.currentJob": "Current Job", "dry-wash.arm.master.table.header.currentJob": "Current Job",
"dry-wash.arm.master.table.header.phone": "Phone", "dry-wash.arm.master.table.header.phone": "Phone",
"dry-wash.arm.master.table.header.actions": "Actions", "dry-wash.arm.master.table.header.actions": "Actions",

View File

@ -13,7 +13,11 @@
"dry-wash.arm.order.table.header.status": "Статус", "dry-wash.arm.order.table.header.status": "Статус",
"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.error.title": "Ошибка при загрузке данных",
"dry-wash.arm.master.title": "Мастера", "dry-wash.arm.master.title": "Мастера",
"dry-wash.arm.master.table.empty": "Список пуст",
"dry-wash.arm.master.error.title": "Ошибка при загрузке данных",
"dry-wash.arm.master.table.header.name": "Имя", "dry-wash.arm.master.table.header.name": "Имя",
"dry-wash.arm.master.table.header.currentJob": "Актуальная занятость", "dry-wash.arm.master.table.header.currentJob": "Актуальная занятость",
"dry-wash.arm.master.table.header.phone": "Телефон", "dry-wash.arm.master.table.header.phone": "Телефон",

34
src/api/arm.ts Normal file
View File

@ -0,0 +1,34 @@
import { getConfigValue } from '@brojs/cli';
enum ArmEndpoints {
ORDERS = '/arm/orders',
MASTERS = '/arm/masters',
}
const armService = () => {
const endpoint = getConfigValue('dry-wash.api');
const fetchOrders = async () => {
const response = await fetch(`${endpoint}${ArmEndpoints.ORDERS}`);
if (!response.ok) {
throw new Error(`Failed to fetch orders: ${response.status}`);
}
return await response.json();
};
const fetchMasters = async () => {
const response = await fetch(`${endpoint}${ArmEndpoints.MASTERS}`);
if (!response.ok) {
throw new Error(`Failed to fetch masters: ${response.status}`);
}
return await response.json();
};
return { fetchOrders, fetchMasters };
};
export { armService, ArmEndpoints };

View File

@ -4,6 +4,19 @@ 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 = {
id: string;
name: string;
schedule: Schedule[];
phone: string;
};
const MasterItem = ({ name, schedule, phone }) => { const MasterItem = ({ name, schedule, phone }) => {
return ( return (
<Tr> <Tr>

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { import {
Box, Box,
Heading, Heading,
@ -10,12 +10,17 @@ import {
Button, Button,
useDisclosure, useDisclosure,
Flex, Flex,
useToast,
Td,
Text,
Spinner,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import MasterItem from '../MasterItem'; import MasterItem from '../MasterItem';
import MasterDrawer from '../MasterDrawer'; import MasterDrawer from '../MasterDrawer';
import data from '../../../stubs/json/arm-masters/success.json'; import { armService } from '../../api/arm';
import { MasterProps } from '../MasterItem/MasterItem';
const TABLE_HEADERS = [ const TABLE_HEADERS = [
'name' as const, 'name' as const,
@ -26,11 +31,41 @@ const TABLE_HEADERS = [
const Masters = () => { const Masters = () => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const toast = useToast();
const { t } = useTranslation('~', { const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.master', keyPrefix: 'dry-wash.arm.master',
}); });
const [masters, setMasters] = useState<MasterProps[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { fetchMasters } = armService();
useEffect(() => {
const loadMasters = async () => {
setLoading(true);
try {
const data = await fetchMasters();
setMasters(data.body);
} catch (err) {
setError(err.message);
toast({
title: t('error.title'),
status: 'error',
duration: 5000,
isClosable: true,
position: 'bottom-right',
});
} finally {
setLoading(false);
}
};
loadMasters();
}, [toast, t]);
return ( return (
<Box p='8'> <Box p='8'>
<Flex justifyContent='space-between' alignItems='center' mb='5'> <Flex justifyContent='space-between' alignItems='center' mb='5'>
@ -48,9 +83,25 @@ const Masters = () => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{data.body.map((master, index) => ( {loading && (
<MasterItem key={index} {...master} /> <Tr>
))} <Td colSpan={TABLE_HEADERS.length} textAlign='center' py='8'>
<Spinner size='lg' />
</Td>
</Tr>
)}
{!loading && masters.length === 0 && !error && (
<Tr>
<Td colSpan={TABLE_HEADERS.length}>
<Text>{t('table.empty')}</Text>
</Td>
</Tr>
)}
{!loading &&
!error &&
masters.map((master, index) => (
<MasterItem key={index} {...master} />
))}
</Tbody> </Tbody>
</Table> </Table>
<MasterDrawer isOpen={isOpen} onClose={onClose} /> <MasterDrawer isOpen={isOpen} onClose={onClose} />

View File

@ -1,24 +1,68 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { Box, Heading, Table, Thead, Tbody, Tr, Th } from '@chakra-ui/react'; import {
Box,
Heading,
Table,
Thead,
Tbody,
Tr,
Th,
Spinner,
Text,
Td,
useToast,
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import OrderItem from '../OrderItem'; import OrderItem from '../OrderItem';
import { OrderProps } from '../OrderItem/OrderItem'; import { OrderProps } from '../OrderItem/OrderItem';
import data from '../../../stubs/json/arm-orders/success.json'; import { armService } from '../../api/arm';
const TABLE_HEADERS = [
'carNumber' as const,
'washingTime' as const,
'orderDate' as const,
'status' as const,
'telephone' as const,
'location' as const,
];
const Orders = () => { const Orders = () => {
const { t } = useTranslation('~', { const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.order', keyPrefix: 'dry-wash.arm.order',
}); });
const TABLE_HEADERS = [ const { fetchOrders } = armService();
'carNumber' as const,
'washingTime' as const, const toast = useToast();
'orderDate' as const,
'status' as const, const [orders, setOrders] = useState<OrderProps[]>([]);
'telephone' as const, const [loading, setLoading] = useState(false);
'location' as const, const [error, setError] = useState<string | null>(null);
];
useEffect(() => {
const loadOrders = async () => {
setLoading(true);
try {
const data = await fetchOrders();
setOrders(data.body);
} catch (err) {
setError(err.message);
toast({
title: t('error.title'),
status: 'error',
duration: 5000,
isClosable: true,
position: 'bottom-right',
});
} finally {
setLoading(false);
}
};
loadOrders();
}, [toast, t]);
return ( return (
<Box p='8'> <Box p='8'>
@ -34,13 +78,29 @@ const Orders = () => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{data.body.map((order, index) => ( {loading && (
<OrderItem <Tr>
key={index} <Td colSpan={TABLE_HEADERS.length} textAlign='center' py='8'>
{...order} <Spinner size='lg' />
status={order.status as OrderProps['status']} </Td>
/> </Tr>
))} )}
{!loading && orders.length === 0 && !error && (
<Tr>
<Td colSpan={TABLE_HEADERS.length}>
<Text>{t('table.empty')}</Text>
</Td>
</Tr>
)}
{!loading &&
!error &&
orders.map((order, index) => (
<OrderItem
key={index}
{...order}
status={order.status as OrderProps['status']}
/>
))}
</Tbody> </Tbody>
</Table> </Table>
</Box> </Box>

View File

@ -6,7 +6,10 @@ const STUBS = require('./admin').STUBS;
const commonError = { success: false, message: 'Что-то пошло не так' }; const commonError = { success: false, message: 'Что-то пошло не так' };
const sleep = (duration = 300) => (req, res, next) => setTimeout(next, duration); const sleep =
(duration = 1000) =>
(req, res, next) =>
setTimeout(next, duration);
router.use(sleep()); router.use(sleep());
@ -32,4 +35,4 @@ router.get('/arm/orders', (req, res) => {
router.use('/admin', require('./admin')); router.use('/admin', require('./admin'));
module.exports = router; module.exports = router;