Merge pull request 'feature/successJson' (#45) from feature/successJson into main
All checks were successful
it-academy/dry-wash-pl/pipeline/head This commit looks good

Reviewed-on: #45
This commit is contained in:
Primakov Alexandr Alexandrovich 2024-12-08 11:18:35 +03:00
commit 48d076a829
9 changed files with 262 additions and 25 deletions

View File

@ -24,6 +24,6 @@ module.exports = {
},
},
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.arm.master.add": "Add",
"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.complete": "Completed",
"dry-wash.arm.order.status.pending": "Pending",
@ -30,6 +32,8 @@
"dry-wash.arm.order.table.header.location": "Location",
"dry-wash.arm.master.title": "Masters",
"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.phone": "Phone",
"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.telephone": "Телефон",
"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.table.empty": "Список пуст",
"dry-wash.arm.master.error.title": "Ошибка при загрузке данных",
"dry-wash.arm.master.table.header.name": "Имя",
"dry-wash.arm.master.table.header.currentJob": "Актуальная занятость",
"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 { 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 }) => {
return (
<Tr>

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import {
Box,
Heading,
@ -10,12 +10,17 @@ import {
Button,
useDisclosure,
Flex,
useToast,
Td,
Text,
Spinner,
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import MasterItem from '../MasterItem';
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 = [
'name' as const,
@ -26,11 +31,41 @@ const TABLE_HEADERS = [
const Masters = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
const toast = useToast();
const { t } = useTranslation('~', {
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 (
<Box p='8'>
<Flex justifyContent='space-between' alignItems='center' mb='5'>
@ -48,7 +83,23 @@ const Masters = () => {
</Tr>
</Thead>
<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} />
))}
</Tbody>

View File

@ -1,15 +1,22 @@
import React from 'react';
import { Box, Heading, Table, Thead, Tbody, Tr, Th } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react';
import {
Box,
Heading,
Table,
Thead,
Tbody,
Tr,
Th,
Spinner,
Text,
Td,
useToast,
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import OrderItem from '../OrderItem';
import { OrderProps } from '../OrderItem/OrderItem';
import data from '../../../stubs/json/arm-orders/success.json';
const Orders = () => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.order',
});
import { armService } from '../../api/arm';
const TABLE_HEADERS = [
'carNumber' as const,
@ -20,6 +27,43 @@ const Orders = () => {
'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 (
<Box p='8'>
<Heading size='lg' mb='5'>
@ -34,7 +78,23 @@ const Orders = () => {
</Tr>
</Thead>
<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
key={index}
{...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 */
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;