Compare commits

..

4 Commits

Author SHA1 Message Date
6442f1f407 Merge pull request 'feat: add dynamic routing (#25)' (#42) from feature/router-arm-dynamic into main
All checks were successful
it-academy/dry-wash-pl/pipeline/head This commit looks good
Reviewed-on: #42
Reviewed-by: Primakov Alexandr Alexandrovich <primakovpro@gmail.com>
2024-11-24 19:54:27 +03:00
b5f3838536 feat: separation of imports (#25)
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-11-24 14:54:01 +03:00
dee3a04310 feat: add dynamic routing (#25)
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
2024-11-24 14:50:51 +03:00
c2fad8a3ff Merge pull request 'feat: add multilingualism (#35)' (#39) from feature/i18next-multilingualism into main
All checks were successful
it-academy/dry-wash-pl/pipeline/head This commit looks good
Reviewed-on: #39
Reviewed-by: Primakov Alexandr Alexandrovich <primakovpro@gmail.com>
2024-11-24 11:22:09 +03:00
16 changed files with 136 additions and 149 deletions

View File

@@ -1,9 +1,9 @@
/* eslint-disable no-undef */ /* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-require-imports */ /* eslint-disable @typescript-eslint/no-require-imports */
const pkg = require("./package"); const pkg = require('./package');
module.exports = { module.exports = {
apiPath: "stubs/api", apiPath: 'stubs/api',
webpackConfig: { webpackConfig: {
output: { output: {
publicPath: `/static/${pkg.name}/${process.env.VERSION || pkg.version}/`, publicPath: `/static/${pkg.name}/${process.env.VERSION || pkg.version}/`,
@@ -11,17 +11,19 @@ module.exports = {
}, },
/* use https://admin.bro-js.ru/ to create config, navigations and features */ /* use https://admin.bro-js.ru/ to create config, navigations and features */
navigations: { navigations: {
"dry-wash.main": "/dry-wash", 'dry-wash.main': '/dry-wash',
"dry-wash.create": "/order", 'dry-wash.create': '/order',
"dry-wash.view.order": "/order/:orderId", 'dry-wash.view.order': '/order/:orderId',
"dry-wash.arm": "/arm", 'dry-wash.arm.master': '/master',
'dry-wash.arm.order': '/order',
'dry-wash.arm': '/arm/*',
}, },
features: { features: {
"dry-wash-pl": { 'dry-wash-pl': {
// add your features here in the format [featureName]: { value: string } // add your features here in the format [featureName]: { value: string }
}, },
}, },
config: { config: {
"dry-wash-pl.api": "/api", 'dry-wash-pl.api': '/api',
}, },
}; };

View File

@@ -49,6 +49,5 @@
"dry-wash.notFound.button.back": "Вернуться на главную", "dry-wash.notFound.button.back": "Вернуться на главную",
"dry-wash.errorBoundary.title":"Что-то пошло не так", "dry-wash.errorBoundary.title":"Что-то пошло не так",
"dry-wash.errorBoundary.description": "Мы уже работаем над исправлением проблемы", "dry-wash.errorBoundary.description": "Мы уже работаем над исправлением проблемы",
"dry-wash.errorBoundary.button.reload": "Перезагрузить страницу", "dry-wash.errorBoundary.button.reload": "Перезагрузить страницу"
"dry-wash.washTime.timeSlot": "{{start}} - {{end}}"
} }

6
package-lock.json generated
View File

@@ -17,7 +17,6 @@
"@fontsource/open-sans": "^5.1.0", "@fontsource/open-sans": "^5.1.0",
"@lottiefiles/react-lottie-player": "^3.5.4", "@lottiefiles/react-lottie-player": "^3.5.4",
"@types/react": "^18.3.12", "@types/react": "^18.3.12",
"dayjs": "^1.11.13",
"express": "^4.21.1", "express": "^4.21.1",
"framer-motion": "^6.2.8", "framer-motion": "^6.2.8",
"i18next": "^23.16.4", "i18next": "^23.16.4",
@@ -5305,11 +5304,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.7", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",

View File

@@ -25,7 +25,6 @@
"@fontsource/open-sans": "^5.1.0", "@fontsource/open-sans": "^5.1.0",
"@lottiefiles/react-lottie-player": "^3.5.4", "@lottiefiles/react-lottie-player": "^3.5.4",
"@types/react": "^18.3.12", "@types/react": "^18.3.12",
"dayjs": "^1.11.13",
"express": "^4.21.1", "express": "^4.21.1",
"framer-motion": "^6.2.8", "framer-motion": "^6.2.8",
"i18next": "^23.16.4", "i18next": "^23.16.4",

View File

@@ -1,25 +1,40 @@
import { generatePath } from "react-router-dom"; import { generatePath } from 'react-router-dom';
import { getNavigationValue } from "@brojs/cli"; import { getNavigationValue } from '@brojs/cli';
import { Order } from "../models"; import { Order } from '../models';
const getFullUrls = (url: string) =>
`${getNavigationValue('dry-wash.main')}${url}`;
export const URLs = { export const URLs = {
landing: { landing: {
url: getNavigationValue("dry-wash.main"), url: getNavigationValue('dry-wash.main'),
getUrl() { getUrl() {
return this.url; return this.url;
} },
}, },
orderForm: { orderForm: {
url: getNavigationValue("dry-wash.create"), url: getNavigationValue('dry-wash.create'),
getUrl() { getUrl() {
return this.url; return this.url;
} },
}, },
orderView: { orderView: {
url: getNavigationValue("dry-wash.view.order"), url: getNavigationValue('dry-wash.view.order'),
getUrl(orderId: Order.Id) { getUrl(orderId: Order.Id) {
return generatePath(this.url, { orderId }); return generatePath(this.url, { orderId });
} },
} },
}; armMaster: {
url: getNavigationValue('dry-wash.arm.master'),
isOn: Boolean(getNavigationValue('dry-wash.arm.master')),
},
armOrder: {
url: getNavigationValue('dry-wash.arm.order'),
isOn: Boolean(getNavigationValue('dry-wash.arm.order')),
},
armBase: {
url: getFullUrls(getNavigationValue('dry-wash.arm')),
isOn: Boolean(getNavigationValue('dry-wash.arm')),
},
};

View File

@@ -5,20 +5,33 @@ import { Navigate, Route, Routes } from 'react-router-dom';
import Sidebar from '../Sidebar'; import Sidebar from '../Sidebar';
import Orders from '../Orders'; import Orders from '../Orders';
import Masters from '../Masters'; import Masters from '../Masters';
import { URLs } from '../../__data__/urls';
const LayoutArm = () => ( const LayoutArm = () => {
<Flex h='100vh'> let defaultRedirect = null;
<Sidebar />
<Box flex='1' bg='gray.50'> if (URLs.armOrder.isOn) {
<Routes> defaultRedirect = URLs.armOrder.url;
<Route> } else if (URLs.armMaster.isOn) {
<Route index element={<Navigate to='orders' replace />} /> defaultRedirect = URLs.armMaster.url;
<Route path='orders' element={<Orders />} /> }
<Route path='masters' element={<Masters />} />
</Route> return (
</Routes> <Flex h='100vh'>
</Box> <Sidebar />
</Flex> <Box flex='1' bg='gray.50'>
); <Routes>
<Route index element={<Navigate to={defaultRedirect} replace />} />
{URLs.armOrder.isOn && (
<Route path={URLs.armOrder.url} element={<Orders />} />
)}
{URLs.armMaster.isOn && (
<Route path={URLs.armMaster.url} element={<Masters />} />
)}
</Routes>
</Box>
</Flex>
);
};
export default LayoutArm; export default LayoutArm;

View File

@@ -2,7 +2,6 @@ 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/date-helpers';
const MasterItem = ({ name, schedule, phone }) => { const MasterItem = ({ name, schedule, phone }) => {
return ( return (
@@ -10,9 +9,9 @@ const MasterItem = ({ name, schedule, phone }) => {
<Td>{name}</Td> <Td>{name}</Td>
<Td> <Td>
<Stack direction='row'> <Stack direction='row'>
{schedule.map(({ startWashTime, endWashTime }, index) => ( {schedule.map((time, index) => (
<Badge colorScheme={'green'} key={index}> <Badge colorScheme={'green'} key={index}>
{getTimeSlot(startWashTime, endWashTime)} {time}
</Badge> </Badge>
))} ))}
</Stack> </Stack>

View File

@@ -13,9 +13,9 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { mastersData } from '../../mocks';
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';
const TABLE_HEADERS = [ const TABLE_HEADERS = [
'name' as const, 'name' as const,
@@ -48,7 +48,7 @@ const Masters = () => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{data.body.map((master, index) => ( {mastersData.map((master, index) => (
<MasterItem key={index} {...master} /> <MasterItem key={index} {...master} />
))} ))}
</Tbody> </Tbody>

View File

@@ -1,9 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Td, Tr, Link, Select } from '@chakra-ui/react'; import { Td, Tr, Link, Select } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs';
import { getTimeSlot } from '../../lib/date-helpers';
const statuses = [ const statuses = [
'pending' as const, 'pending' as const,
@@ -18,8 +15,7 @@ type GetArrItemType<ArrType> =
export type OrderProps = { export type OrderProps = {
carNumber?: string; carNumber?: string;
startWashTime?: string; washTime?: string;
endWashTime?: string;
orderDate?: string; orderDate?: string;
status?: GetArrItemType<typeof statuses>; status?: GetArrItemType<typeof statuses>;
phone?: string; phone?: string;
@@ -28,8 +24,7 @@ export type OrderProps = {
const OrderItem = ({ const OrderItem = ({
carNumber, carNumber,
startWashTime, washTime,
endWashTime,
orderDate, orderDate,
status, status,
phone, phone,
@@ -44,8 +39,8 @@ const OrderItem = ({
return ( return (
<Tr> <Tr>
<Td>{carNumber}</Td> <Td>{carNumber}</Td>
<Td>{getTimeSlot(startWashTime, endWashTime)}</Td> <Td>{washTime}</Td>
<Td>{dayjs(orderDate).format('DD.MM.YYYY')}</Td> <Td>{orderDate}</Td>
<Td> <Td>
<Select <Select
value={statusSelect} value={statusSelect}

View File

@@ -2,9 +2,9 @@ import React from 'react';
import { Box, Heading, Table, Thead, Tbody, Tr, Th } from '@chakra-ui/react'; import { Box, Heading, Table, Thead, Tbody, Tr, Th } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ordersData } from '../../mocks';
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';
const Orders = () => { const Orders = () => {
const { t } = useTranslation('~', { const { t } = useTranslation('~', {
@@ -34,7 +34,7 @@ const Orders = () => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{data.body.map((order, index) => ( {ordersData.map((order, index) => (
<OrderItem <OrderItem
key={index} key={index}
{...order} {...order}

View File

@@ -3,6 +3,8 @@ import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { URLs } from '../../__data__/urls';
const Sidebar = () => { const Sidebar = () => {
const { t } = useTranslation('~', { const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.master.sideBar', keyPrefix: 'dry-wash.arm.master.sideBar',
@@ -23,25 +25,29 @@ const Sidebar = () => {
<VStack align='start' spacing='4'> <VStack align='start' spacing='4'>
<Divider /> <Divider />
<Button {URLs.armOrder.isOn && (
as={Link} <Button
to='orders' as={Link}
w='100%' to={URLs.armOrder.url}
colorScheme='green' w='100%'
variant='ghost' colorScheme='green'
> variant='ghost'
{t('orders')} >
</Button> {t('orders')}
</Button>
)}
<Divider /> <Divider />
<Button {URLs.armMaster.isOn && (
as={Link} <Button
to='masters' as={Link}
w='100%' to={URLs.armMaster.url}
colorScheme='green' w='100%'
variant='ghost' colorScheme='green'
> variant='ghost'
{t('master')} >
</Button> {t('master')}
</Button>
)}
<Divider /> <Divider />
</VStack> </VStack>
</Box> </Box>

View File

@@ -1,12 +0,0 @@
import dayjs from 'dayjs';
import i18next from 'i18next';
export const getTimeSlot = (start: string, end: string) => {
const startDay = dayjs(start).format('HH:mm');
const endDay = dayjs(end).format('HH:mm');
return i18next.t('~:dry-wash.washTime.timeSlot', {
start: startDay,
end: endDay,
});
};

35
src/mocks/index.ts Normal file
View File

@@ -0,0 +1,35 @@
export const mastersData = [
{
id: 'masters1',
name: 'Иван Иванов',
schedule: ['15:00 - 16:30', '17:00 - 18:00'],
phone: '+7 900 123 45 67',
},
{
id: 'masters2',
name: 'Сергей Петров',
schedule: ['10:00 - 12:30', '14:00 - 15:30', '16:00 - 17:00'],
phone: '+7 900 234 56 78',
},
];
export const ordersData = [
{
id: 'order1',
carNumber: 'A123BC',
washTime: '10:30',
orderDate: '2024-10-31',
status: 'progress',
phone: '79001234567',
location: 'Казань, ул. Баумана, 1',
},
{
id: 'order2',
carNumber: 'B456CD',
washTime: '11:00',
orderDate: '2024-10-31',
status: 'complete',
phone: '79002345678',
location: 'Казань, ул. Кремлёвская, 3',
},
];

View File

@@ -17,7 +17,9 @@ const Routers = () => {
<Route path={URLs.landing.url} element={<Landing />} /> <Route path={URLs.landing.url} element={<Landing />} />
<Route path={URLs.orderForm.url} element={<OrderForm />} /> <Route path={URLs.orderForm.url} element={<OrderForm />} />
<Route path={URLs.orderView.url} element={<OrderView />} /> <Route path={URLs.orderView.url} element={<OrderView />} />
<Route path='/dry-wash/arm/*' element={<Arm />}></Route> {URLs.armBase.isOn && (
<Route path={URLs.armBase.url} element={<Arm />}></Route>
)}
<Route path='*' element={<NotFound />} /> <Route path='*' element={<NotFound />} />
</Routes> </Routes>
</Suspense> </Suspense>

View File

@@ -1,35 +0,0 @@
{
"success": true,
"body": [
{
"id": "masters1",
"name": "Иван Иванов",
"schedule": [ {
"id": "order1",
"startWashTime": "2024-11-24T10:30:00.000Z",
"endWashTime": "2024-11-24T16:30:00.000Z"
},
{
"id": "order2",
"startWashTime": "2024-11-24T11:30:00.000Z",
"endWashTime": "2024-11-24T17:30:00.000Z"
}],
"phone": "+7 900 123 45 67"
},
{
"id": "masters12",
"name": "Иван Иванов",
"schedule": [ {
"id": "order1",
"startWashTime": "2024-11-24T10:30:00.000Z",
"endWashTime": "2024-11-24T16:30:00.000Z"
},
{
"id": "order2",
"startWashTime": "2024-11-24T11:30:00.000Z",
"endWashTime": "2024-11-24T17:30:00.000Z"
}],
"phone": "+7 900 123 45 67"
}
]
}

View File

@@ -1,25 +0,0 @@
{
"success": true,
"body": [
{
"id": "order1",
"carNumber": "A123BC",
"startWashTime": "2024-11-24T10:30:00.000Z",
"endWashTime": "2024-11-24T16:30:00.000Z",
"orderDate": "2024-11-24T08:41:46.366Z",
"status": "progress",
"phone": "79001234563",
"location": "Казань, ул. Баумана, 1"
},
{
"id": "order2",
"carNumber": "A245BC",
"startWashTime": "2024-11-24T11:30:00.000Z",
"endWashTime": "2024-11-24T17:30:00.000Z",
"orderDate": "2024-11-24T07:40:46.366Z",
"status": "progress",
"phone": "79001234567",
"location": "Казань, ул. Баумана, 43"
}
]
}