feature/successJson #45

Merged
primakov merged 4 commits from feature/successJson into main 2024-12-08 11:18:36 +03:00
9 changed files with 262 additions and 25 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,7 +83,23 @@ const Masters = () => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{data.body.map((master, index) => ( {loading && (
<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} /> <MasterItem key={index} {...master} />
))} ))}
</Tbody> </Tbody>

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 Orders = () => { const TABLE_HEADERS = [
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.order',
});
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,
'telephone' as const, 'telephone' as const,
'location' as const, 'location' as const,
]; ];
const Orders = () => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.order',
});
const { fetchOrders } = armService();
const toast = useToast();
const [orders, setOrders] = useState<OrderProps[]>([]);
const [loading, setLoading] = useState(false);
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,7 +78,23 @@ const Orders = () => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{data.body.map((order, index) => ( {loading && (
<Tr>
<Td colSpan={TABLE_HEADERS.length} textAlign='center' py='8'>
<Spinner size='lg' />
</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 <OrderItem
key={index} key={index}
{...order} {...order}

38
stubs/api/admin.js Normal file
View File

@ -0,0 +1,38 @@
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-require-imports */
const router = require('express').Router();
const STUBS = { masters: 'success', orders: 'success' };
router.get('/set/:name/:value', (req, res) => {
const { name, value } = req.params;
STUBS[name] = value;
res.send('ok');
});
router.get('/', (req, res) => {
res.send(`<div>
<fieldset>
<legend>Мастера</legend>
${generateRadioInput('masters', 'success')}
${generateRadioInput('masters', 'error')}
</fieldset>
<fieldset>
<legend>Заказы</legend>
${generateRadioInput('orders', 'success')}
${generateRadioInput('orders', 'error')}
</fieldset>
</div>`);
});
module.exports = router;
module.exports.STUBS = STUBS;
function generateRadioInput(name, type) {
return `<label>
<input ${STUBS[name] === type ? 'checked' : ''} onclick="fetch('/api/admin/set/${name}/${type}')" type="radio" name="${name}">
${type}
</label>`;
}

View File

@ -2,4 +2,37 @@
/* eslint-disable @typescript-eslint/no-require-imports */ /* eslint-disable @typescript-eslint/no-require-imports */
const router = require('express').Router(); const router = require('express').Router();
const STUBS = require('./admin').STUBS;
const commonError = { success: false, message: 'Что-то пошло не так' };
const sleep =
(duration = 1000) =>
(req, res, next) =>
setTimeout(next, duration);
router.use(sleep());
router.get('/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.get('/arm/orders', (req, res) => {
res
.status(/error/.test(STUBS.orders) ? 500 : 200)
.send(
/^error$/.test(STUBS.orders)
? commonError
: require(`../json/arm-orders/${STUBS.orders}.json`),
);
});
router.use('/admin', require('./admin'));
module.exports = router; module.exports = router;