Compare commits

...

28 Commits

Author SHA1 Message Date
2130532dfd feat: separation of imports MasterItem.tsx (#40)
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:59:38 +03:00
3b4f5a8486 feat: separation of imports (#40)
Some checks failed
it-academy/dry-wash-pl/pipeline/pr-main There was a failure building this commit
2024-11-24 14:57:36 +03:00
acd39b1e32 feat: add format date(#40)
Some checks failed
it-academy/dry-wash-pl/pipeline/pr-main There was a failure building this commit
2024-11-24 12:08:45 +03:00
920f4440e9 fix: divide the imports(#37)
All checks were successful
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
2024-11-23 15:59:21 +03:00
e374cbfdd3 feat: add stub success(#37)
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-11-23 15:56:40 +03:00
0b30ce6571 feat: divide imports by types (#35)
All checks were successful
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
2024-11-23 15:37:06 +03:00
3b47e1dcdf Merge remote-tracking branch 'origin/feature/i18next-multilingualism' into feature/i18next-multilingualism
Some checks failed
it-academy/dry-wash-pl/pipeline/pr-main There was a failure building this commit
2024-11-23 15:28:44 +03:00
4e8bc2fca5 feat: add multilingualism (#35) 2024-11-23 15:27:53 +03:00
0c9663fcef feat: add multilingualism (#35)
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 This commit looks good
2024-11-23 15:19:44 +03:00
a63304b5e4 Merge pull request 'feature/eslint-import-order' (#32) from feature/eslint-import-order into main
All checks were successful
it-academy/dry-wash-pl/pipeline/head This commit looks good
Reviewed-on: #32
Reviewed-by: Primakov Alexandr Alexandrovich <primakovpro@gmail.com>
2024-11-17 13:56:21 +03:00
RustamRu
effd9dae2d Merge branch 'feature/eslint-import-order' of ssh://85.143.175.152:222/dry_wash_inc/dry-wash-pl into feature/eslint-import-order
All checks were successful
it-academy/dry-wash-pl/pipeline/head This commit looks good
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
2024-11-17 13:46:28 +03:00
RustamRu
1787e94f8e feat: update eslint import order rule (#31) 2024-11-17 13:46:24 +03:00
RustamRu
855ec2f138 feat: update eslint import order rule
All checks were successful
it-academy/dry-wash-pl/pipeline/head This commit looks good
2024-11-17 13:43:41 +03:00
RustamRu
d15d761f1a 0.1.0
All checks were successful
it-academy/dry-wash-pl/pipeline/pr-feat/landing This commit looks good
2024-11-17 11:44:54 +03:00
05303092e2 Merge pull request 'feat/landing' (#30) from feat/landing into main
All checks were successful
it-academy/dry-wash-pl/pipeline/pr-feat/landing This commit looks good
Reviewed-on: #30
2024-11-17 11:39:24 +03:00
RustamRu
98181ff354 update brojs/cli
All checks were successful
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
2024-11-17 11:28:04 +03:00
RustamRu
5822f5f7b0 Merge remote-tracking branch 'origin/main' into feat/landing
All checks were successful
it-academy/dry-wash-pl/pipeline/head This commit looks good
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
2024-11-17 11:22:38 +03:00
edc329c154 Merge pull request 'feature/page404' (#27) from feature/page404 into main
Some checks failed
it-academy/dry-wash-pl/pipeline/pr-feat/landing There was a failure building this commit
Reviewed-on: #27
2024-11-17 11:17:51 +03:00
5d208b1fc5 feat: delete word test(#22)
Some checks are pending
it-academy/dry-wash-pl/pipeline/pr-main Build started...
it-academy/dry-wash-pl/pipeline/head This commit looks good
2024-11-17 11:16:23 +03:00
1839136c9f feat: add not found page (#22) 2024-11-17 11:16:16 +03:00
a6ec94785a feat: test (#22) 2024-11-17 11:14:09 +03:00
18ca96bfe9 Merge pull request 'feat: add ErrorBoundary page (#23)' (#28) from feature/errorBoundary into main
Some checks failed
it-academy/dry-wash-pl/pipeline/pr-feat/landing There was a failure building this commit
Reviewed-on: #28
2024-11-17 11:07:38 +03:00
db918ba7c7 Merge pull request 'feat: add router to the arm page (#14)' (#20) from feature/router-arm into main
Some checks failed
it-academy/dry-wash-pl/pipeline/pr-feat/landing There was a failure building this commit
Reviewed-on: #20
2024-11-17 11:02:45 +03:00
d1d92c63e8 feat: add ErrorBoundary page (#23)
All checks were successful
it-academy/dry-wash-pl/pipeline/head This commit looks good
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
2024-11-16 20:09:02 +03:00
dedc7e1608 feat: convert link to button (#14)
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-16 18:24:51 +03:00
1c8348dee5 Merge remote-tracking branch 'origin/feature/router-arm' into feature/router-arm
All checks were successful
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
# Conflicts:
#	src/components/Sidebar/Sidebar.tsx
#	src/routes.tsx
2024-11-16 17:26:22 +03:00
04e4d011ff feat: add router to the arm page (#14) 2024-11-16 17:05:21 +03:00
116e883e91 feat: add router to the arm page (#14) 2024-11-09 19:34:12 +03:00
36 changed files with 1283 additions and 263 deletions

View File

@@ -3,6 +3,7 @@ import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";
import stylistic from '@stylistic/eslint-plugin';
import pluginImport from 'eslint-plugin-import';
export default [
{ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] },
@@ -12,7 +13,8 @@ export default [
pluginReact.configs.flat.recommended,
{
plugins: {
'@stylistic': stylistic
'@stylistic': stylistic,
'import': pluginImport,
},
"rules": {
"no-unused-vars": "off",
@@ -24,13 +26,20 @@ export default [
"caughtErrorsIgnorePattern": "^_"
}
],
"sort-imports": ["error", {
"ignoreCase": false,
"ignoreDeclarationSort": true,
"ignoreMemberSort": true,
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
"allowSeparatedGroups": true
}],
"sort-imports": ["off"],
"import/order": [
"error",
{
"groups": [
"builtin",
"external",
"internal",
"parent",
["sibling", "index"]
],
"newlines-between": "always",
}
],
semi: ["error", "always"],
'@stylistic/indent': ['error', 2],
'react/prop-types': 'off'

View File

@@ -13,5 +13,41 @@
"dry-wash.landing.hero-section.headline": "Revitalize Your Ride with Eco-Friendly Care!",
"dry-wash.landing.make-order-button": "Make order",
"dry-wash.landing.site-logo": "The logo of the \"Dry Master\" company",
"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.order.title": "Orders",
"dry-wash.arm.order.status.progress": "In Progress",
"dry-wash.arm.order.status.complete": "Completed",
"dry-wash.arm.order.status.pending": "Pending",
"dry-wash.arm.order.status.working": "Working",
"dry-wash.arm.order.status.canceled": "Canceled",
"dry-wash.arm.order.status.placeholder": "Select Status",
"dry-wash.arm.order.table.header.carNumber": "Car Number",
"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.telephone": "Telephone",
"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.header.currentJob": "Current Job",
"dry-wash.arm.master.table.header.phone": "Phone",
"dry-wash.arm.master.table.header.actions": "Actions",
"dry-wash.arm.master.table.actionsMenu.delete": "Delete Master",
"dry-wash.arm.master.drawer.title": "Add New Master",
"dry-wash.arm.master.drawer.inputName.label": "Full Name",
"dry-wash.arm.master.drawer.inputName.placeholder": "Enter Full Name",
"dry-wash.arm.master.drawer.inputPhone.label": "Phone Number",
"dry-wash.arm.master.drawer.inputPhone.placeholder": "Enter Phone Number",
"dry-wash.arm.master.drawer.button.save": "Save",
"dry-wash.arm.master.drawer.button.cancel": "Cancel",
"dry-wash.arm.master.sideBar.orders": "Orders",
"dry-wash.arm.master.sideBar.master": "Masters",
"dry-wash.arm.master.sideBar.title": "Dry Master",
"dry-wash.notFound.title": "Page Not Found",
"dry-wash.notFound.description": "Unfortunately, the page you are looking for does not exist.",
"dry-wash.notFound.button.back": "Back to Home",
"dry-wash.errorBoundary.title": "Something went wrong",
"dry-wash.errorBoundary.description": "We are already working on fixing the issue",
"dry-wash.errorBoundary.button.reload": "Reload Page"
}

View File

@@ -3,7 +3,7 @@
"dry-wash.arm.order.title": "Заказы",
"dry-wash.arm.order.status.progress": "Выполняется",
"dry-wash.arm.order.status.complete": "Завершено",
"dry-wash.arm.order.status.pending": "в ожидании",
"dry-wash.arm.order.status.pending": "В ожидании",
"dry-wash.arm.order.status.working": "В работе",
"dry-wash.arm.order.status.canceled": "Отменено",
"dry-wash.arm.order.status.placeholder": "Выберите статус",
@@ -26,9 +26,9 @@
"dry-wash.arm.master.drawer.inputPhone.placeholder": "Введите номер телефона",
"dry-wash.arm.master.drawer.button.save": "Сохранить",
"dry-wash.arm.master.drawer.button.cancel": "Отменить",
"dry-wash.arm.master.sideBar.title": " Сухой мастер",
"dry-wash.arm.master.sideBar.title.master": "Мастера",
"dry-wash.arm.master.sideBar.title.orders": "Заказы",
"dry-wash.arm.master.sideBar.orders": "Заказы",
"dry-wash.arm.master.sideBar.master": "Мастера",
"dry-wash.arm.master.sideBar.title": "Сухой мастер",
"dry-wash.landing.benefits-section.description": "Откройте для себя преимущества наших услуг по химчистке автомобилей",
"dry-wash.landing.benefits-section.heading": "Преимущества экологичной автомойки",
"dry-wash.landing.benefits-section.list.0": "Экологически безопасные продукты",
@@ -43,5 +43,12 @@
"dry-wash.landing.hero-section.headline": "Оживите свою поездку с помощью экологически чистого ухода!",
"dry-wash.landing.make-order-button": "Сделать заказ",
"dry-wash.landing.site-logo": "Логотип компании \u00ABDry Master\u00BB",
"dry-wash.landing.social-proof-section.heading": "Нас выбирают"
}
"dry-wash.landing.social-proof-section.heading": "Нас выбирают",
"dry-wash.notFound.title": "Страница не найдена",
"dry-wash.notFound.description": "К сожалению, запрашиваемая вами страница не существует.",
"dry-wash.notFound.button.back": "Вернуться на главную",
"dry-wash.errorBoundary.title":"Что-то пошло не так",
"dry-wash.errorBoundary.description": "Мы уже работаем над исправлением проблемы",
"dry-wash.errorBoundary.button.reload": "Перезагрузить страницу",
"dry-wash.washTime.timeSlot": "{{start}} - {{end}}"
}

933
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "dry-wash",
"version": "0.0.0",
"version": "0.1.0",
"description": "<a id=\"readme-top\"></a>",
"main": "./src/index.tsx",
"scripts": {
@@ -17,13 +17,15 @@
"author": "",
"license": "ISC",
"dependencies": {
"@brojs/cli": "^1.6.2",
"@brojs/cli": "^1.6.3",
"@chakra-ui/icons": "^2.2.4",
"@chakra-ui/react": "^2.4.2",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@fontsource/open-sans": "^5.1.0",
"@lottiefiles/react-lottie-player": "^3.5.4",
"@types/react": "^18.3.12",
"dayjs": "^1.11.13",
"express": "^4.21.1",
"framer-motion": "^6.2.8",
"i18next": "^23.16.4",
@@ -38,6 +40,7 @@
"@stylistic/eslint-plugin": "^2.10.1",
"@types/react-dom": "^18.3.1",
"eslint": "^9.14.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-react": "^7.37.2",
"globals": "^15.11.0",
"prettier": "3.3.3",

View File

@@ -1,5 +1,6 @@
import { generatePath } from "react-router-dom";
import { getNavigationValue } from "@brojs/cli";
import { Order } from "../models";
export const URLs = {

View File

@@ -1,14 +1,18 @@
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import Routers from './routes';
import { ChakraProvider, theme as chakraTheme } from '@chakra-ui/react';
import Routers from './routes';
import ErrorBoundary from './components/ErrorBoundary';
const App = () => {
return (
<ChakraProvider theme={chakraTheme}>
<BrowserRouter>
<Routers></Routers>
</BrowserRouter>
<ErrorBoundary>
<BrowserRouter>
<Routers />
</BrowserRouter>
</ErrorBoundary>
</ChakraProvider>
);
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,65 @@
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { Heading, Text, Button, Center, VStack } from '@chakra-ui/react';
import i18next from 'i18next';
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
errorInfo: ErrorInfo | null;
}
interface ErrorBoundaryProps {
children: ReactNode;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
};
}
static getDerivedStateFromError(): Partial<ErrorBoundaryState> {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
console.error('Error caught by ErrorBoundary:', error, errorInfo);
this.setState({ error, errorInfo });
}
render() {
const { hasError } = this.state;
//TODO: добавить анимацию после залива 404 страницы
//TODO: может сделать обертку для хука, чтоб язык менялся без перезагрузки
if (hasError) {
return (
<Center minH='100vh'>
<VStack spacing={4} textAlign='center'>
<Heading as='h1' size='2xl'>
{i18next.t('~:dry-wash.errorBoundary.title')}
</Heading>
<Text fontSize='lg'>
{i18next.t('~:dry-wash.errorBoundary.description')}
</Text>
<Button
colorScheme='teal'
size='lg'
variant='outline'
onClick={() => window.location.reload()}
>
{i18next.t('~:dry-wash.errorBoundary.button.reload')}
</Button>
</VStack>
</Center>
);
}
return this.props.children;
}
}
export default ErrorBoundary;

View File

@@ -0,0 +1 @@
export { default } from './ErrorBoundary';

View File

@@ -1,15 +1,22 @@
import { Box, Flex } from '@chakra-ui/react';
import React from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import Sidebar from '../Sidebar';
import Orders from '../Orders';
import Masters from '../Masters';
import React from 'react';
const LayoutArm = ({ currentPage, onSelectPage }) => (
const LayoutArm = () => (
<Flex h='100vh'>
<Sidebar onSelectPage={onSelectPage} />
<Sidebar />
<Box flex='1' bg='gray.50'>
{currentPage === 'orders' && <Orders />}
{currentPage === 'masters' && <Masters />}
<Routes>
<Route>
<Route index element={<Navigate to='orders' replace />} />
<Route path='orders' element={<Orders />} />
<Route path='masters' element={<Masters />} />
</Route>
</Routes>
</Box>
</Flex>
);

View File

@@ -7,16 +7,18 @@ import {
IconButton,
} from '@chakra-ui/react';
import { EditIcon } from '@chakra-ui/icons';
import i18next from 'i18next';
import { useTranslation } from 'react-i18next';
const MasterActionsMenu = () => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.master.table.actionsMenu',
});
return (
<Menu>
<MenuButton icon={<EditIcon />} as={IconButton} variant='outline' />
<MenuList>
<MenuItem>
{i18next.t('dry-wash.arm.master.table.actionsMenu.delete')}
</MenuItem>
<MenuItem>{t('delete')}</MenuItem>
</MenuList>
</Menu>
);

View File

@@ -12,7 +12,7 @@ import {
DrawerHeader,
DrawerOverlay,
} from '@chakra-ui/react';
import i18next from 'i18next';
import { useTranslation } from 'react-i18next';
const MasterDrawer = ({ isOpen, onClose }) => {
const [newMaster, setNewMaster] = useState({ name: '', phone: '' });
@@ -22,51 +22,44 @@ const MasterDrawer = ({ isOpen, onClose }) => {
onClose();
};
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.master.drawer',
});
return (
<Drawer isOpen={isOpen} onClose={onClose} size='md'>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>
{i18next.t('dry-wash.arm.master.drawer.title')}
</DrawerHeader>
<DrawerHeader>{t('title')}</DrawerHeader>
<DrawerBody>
<FormControl mb='4'>
<FormLabel>
{i18next.t('dry-wash.arm.master.drawer.inputName.label')}
</FormLabel>
<FormLabel>{t('inputName.label')}</FormLabel>
<Input
value={newMaster.name}
onChange={(e) =>
setNewMaster({ ...newMaster, name: e.target.value })
}
placeholder={i18next.t(
'dry-wash.arm.master.drawer.inputName.placeholder',
)}
placeholder={t('inputName.placeholder')}
/>
</FormControl>
<FormControl>
<FormLabel>
{' '}
{i18next.t('dry-wash.arm.master.drawer.inputPhone.label')}
</FormLabel>
<FormLabel> {t('inputPhone.label')}</FormLabel>
<Input
value={newMaster.phone}
onChange={(e) =>
setNewMaster({ ...newMaster, phone: e.target.value })
}
placeholder={i18next.t(
'dry-wash.arm.master.drawer.inputPhone.placeholder',
)}
placeholder={t('inputPhone.placeholder')}
/>
</FormControl>
</DrawerBody>
<DrawerFooter>
<Button colorScheme='teal' mr={3} onClick={handleSave}>
{i18next.t('dry-wash.arm.master.drawer.button.save')}
{t('button.save')}
</Button>
<Button variant='ghost' onClick={onClose}>
{i18next.t('dry-wash.arm.master.drawer.button.cancel')}
{t('button.cancel')}
</Button>
</DrawerFooter>
</DrawerContent>

View File

@@ -1,6 +1,8 @@
import React from 'react';
import { Badge, Link, Stack, Td, Tr } from '@chakra-ui/react';
import MasterActionsMenu from '../MasterActionsMenu';
import { getTimeSlot } from '../../lib/date-helpers';
const MasterItem = ({ name, schedule, phone }) => {
return (
@@ -8,9 +10,9 @@ const MasterItem = ({ name, schedule, phone }) => {
<Td>{name}</Td>
<Td>
<Stack direction='row'>
{schedule.map((time, index) => (
{schedule.map(({ startWashTime, endWashTime }, index) => (
<Badge colorScheme={'green'} key={index}>
{time}
{getTimeSlot(startWashTime, endWashTime)}
</Badge>
))}
</Stack>

View File

@@ -11,36 +11,44 @@ import {
useDisclosure,
Flex,
} from '@chakra-ui/react';
import { mastersData } from '../../mocks';
import { useTranslation } from 'react-i18next';
import MasterItem from '../MasterItem';
import MasterDrawer from '../MasterDrawer';
import i18next from 'i18next';
import data from '../../../stubs/json/arm-masters/success.json';
const TABLE_HEADERS = ['name', 'currentJob', 'phone', 'actions'];
const TABLE_HEADERS = [
'name' as const,
'currentJob' as const,
'phone' as const,
'actions' as const,
];
const Masters = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.master',
});
return (
<Box p='8'>
<Flex justifyContent='space-between' alignItems='center' mb='5'>
<Heading size='lg'> {i18next.t('dry-wash.arm.master.title')}</Heading>
<Heading size='lg'> {t('title')}</Heading>
<Button colorScheme='green' onClick={onOpen}>
+ {i18next.t('dry-wash.arm.master.add')}
+ {t('add')}
</Button>
</Flex>
<Table variant='simple' colorScheme='blackAlpha'>
<Thead>
<Tr>
{TABLE_HEADERS.map((name) => (
<Th key={name}>
{i18next.t(`dry-wash.arm.master.table.header.${name}`)}
</Th>
<Th key={name}>{t(`table.header.${name}`)}</Th>
))}
</Tr>
</Thead>
<Tbody>
{mastersData.map((master, index) => (
{data.body.map((master, index) => (
<MasterItem key={index} {...master} />
))}
</Tbody>

View File

@@ -1,33 +1,60 @@
import React, { useState } from 'react';
import { Td, Tr, Link, Select } from '@chakra-ui/react';
import i18next from 'i18next';
import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs';
const statuses = ['pending', 'progress', 'working', 'canceled', 'complete'];
import { getTimeSlot } from '../../lib/date-helpers';
const statuses = [
'pending' as const,
'progress' as const,
'working' as const,
'canceled' as const,
'complete' as const,
];
type GetArrItemType<ArrType> =
ArrType extends Array<infer ItemType> ? ItemType : never;
export type OrderProps = {
carNumber?: string;
startWashTime?: string;
endWashTime?: string;
orderDate?: string;
status?: GetArrItemType<typeof statuses>;
phone?: string;
location?: string;
};
const OrderItem = ({
carNumber,
washTime,
startWashTime,
endWashTime,
orderDate,
status,
phone,
location,
}) => {
}: OrderProps) => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.order',
});
const [statusSelect, setStatus] = useState(status);
return (
<Tr>
<Td>{carNumber}</Td>
<Td>{washTime}</Td>
<Td>{orderDate}</Td>
<Td>{getTimeSlot(startWashTime, endWashTime)}</Td>
<Td>{dayjs(orderDate).format('DD.MM.YYYY')}</Td>
<Td>
<Select
value={statusSelect}
onChange={(e) => setStatus(e.target.value)}
placeholder={i18next.t(`dry-wash.arm.order.status.placeholder`)}
onChange={(e) => setStatus(e.target.value as OrderProps['status'])}
placeholder={t(`status.placeholder`)}
>
{statuses.map((status) => (
<option key={status} value={status}>
{i18next.t(`dry-wash.arm.order.status.${status}`)}
{t(`status.${status}`)}
</option>
))}
</Select>

View File

@@ -1,35 +1,45 @@
import { Box, Heading, Table, Thead, Tbody, Tr, Th } from '@chakra-ui/react';
import React from 'react';
import { ordersData } from '../../mocks';
import { Box, Heading, Table, Thead, Tbody, Tr, Th } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import OrderItem from '../OrderItem';
import i18next from 'i18next';
import { OrderProps } from '../OrderItem/OrderItem';
import data from '../../../stubs/json/arm-orders/success.json';
const Orders = () => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.order',
});
const TABLE_HEADERS = [
'carNumber',
'washingTime',
'orderDate',
'status',
'telephone',
'location',
'carNumber' as const,
'washingTime' as const,
'orderDate' as const,
'status' as const,
'telephone' as const,
'location' as const,
];
return (
<Box p='8'>
<Heading size='lg' mb='5'>
{i18next.t('dry-wash.arm.order.title')}
{t('title')}
</Heading>
<Table variant='simple' colorScheme='blackAlpha'>
<Thead>
<Tr>
{TABLE_HEADERS.map((name, key) => (
<Th key={key}>
{i18next.t(`dry-wash.arm.order.table.header.${name}`)}
</Th>
<Th key={key}>{t(`table.header.${name}`)}</Th>
))}
</Tr>
</Thead>
<Tbody>
{ordersData.map((order, index) => (
<OrderItem key={index} {...order} />
{data.body.map((order, index) => (
<OrderItem
key={index}
{...order}
status={order.status as OrderProps['status']}
/>
))}
</Tbody>
</Table>

View File

@@ -1,42 +1,51 @@
import { Box, Button, Heading, VStack } from '@chakra-ui/react';
import { Box, Button, Heading, VStack, Divider } from '@chakra-ui/react';
import React from 'react';
import { Divider } from '@chakra-ui/react';
import i18next from 'i18next';
const Sidebar = ({ onSelectPage }) => (
<Box
borderRight='1px solid black'
bg='gray.50'
color='white'
w='250px'
p='5'
pt='8'
>
<Heading color='green' size='lg' mb='5'>
{i18next.t(`dry-wash.arm.master.sideBar.title`)}
</Heading>
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
<VStack align='start' spacing='4'>
<Divider />
<Button
onClick={() => onSelectPage('orders')}
w='100%'
colorScheme='green'
variant='ghost'
>
{i18next.t(`dry-wash.arm.master.sideBar.title.orders`)}
</Button>
<Divider />
<Button
onClick={() => onSelectPage('masters')}
w='100%'
colorScheme='green'
variant='ghost'
>
{i18next.t(`dry-wash.arm.master.sideBar.title.master`)}
</Button>
<Divider />
</VStack>
</Box>
);
const Sidebar = () => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.master.sideBar',
});
return (
<Box
borderRight='1px solid black'
bg='gray.50'
color='white'
w='250px'
p='5'
pt='8'
>
<Heading color='green' size='lg' mb='5'>
{t('title')}
</Heading>
<VStack align='start' spacing='4'>
<Divider />
<Button
as={Link}
to='orders'
w='100%'
colorScheme='green'
variant='ghost'
>
{t('orders')}
</Button>
<Divider />
<Button
as={Link}
to='masters'
w='100%'
colorScheme='green'
variant='ghost'
>
{t('master')}
</Button>
<Divider />
</VStack>
</Box>
);
};
export default Sidebar;

View File

@@ -7,7 +7,9 @@ import {
MdHandshake,
} from 'react-icons/md';
import { Heading, HStack, List, Text, VStack } from '@chakra-ui/react';
import { CtaButton, PageSection } from '../';
import { ListItem } from './ListItem';
export const BenefitsSection: FC = () => {

View File

@@ -2,6 +2,7 @@ import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { Link as RouterLink } from 'react-router-dom';
import { ButtonProps, Button } from '@chakra-ui/react';
import { URLs } from '../../../__data__/urls';
export const CtaButton: FC<ButtonProps> = (props) => {
@@ -14,7 +15,7 @@ export const CtaButton: FC<ButtonProps> = (props) => {
colorScheme='primary'
{...props}
>
{t('dry-wash.landing.make-order-button')}
{t('~:dry-wash.landing.make-order-button')}
</Button>
);
};

View File

@@ -2,7 +2,9 @@ import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { Link, List, ListItem } from '@chakra-ui/react';
import { Link as RouterLink } from 'react-router-dom';
import { SiteLogo, PageSection } from '../';
import { Copyright } from './Copyright';
export const Footer: FC = () => {

View File

@@ -1,6 +1,7 @@
import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Heading, Text, Center, VStack, BoxProps } from '@chakra-ui/react';
import { DemoVideoPosterImg } from '../../../assets/images';
import { CtaButton, SiteLogo, PageSection } from '../';

View File

@@ -1,12 +1,13 @@
import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { Image } from '@chakra-ui/react';
import { LogoSvg } from '../../../assets/icons';
export const SiteLogo: FC = () => {
const { t } = useTranslation();
return <Image src={LogoSvg} alt={t('dry-wash.landing.site-logo')} w={40} />;
return <Image src={LogoSvg} alt={t('~:dry-wash.landing.site-logo')} w={40} />;
};
// todo: replace Image by SVG React component

View File

@@ -8,8 +8,10 @@ import {
Tabs,
Text,
} from '@chakra-ui/react';
import { mockReview } from '../../../../mocks/landing';
import { Review } from '../../../../models';
import { ReviewCard } from './ReviewCard';
const SLIDE_CHANGE_INTERVAL = 5000;

View File

@@ -1,7 +1,9 @@
import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { Heading, HStack } from '@chakra-ui/react';
import { CtaButton, PageSection } from '../';
import { ReviewsSlider } from './ReviewsSlider';
export const SocialProofSection: FC = () => {

View File

@@ -1,5 +1,6 @@
import React, { FC, PropsWithChildren } from 'react';
import { ChakraProvider } from '@chakra-ui/react';
import { default as landingTheme } from './theme-config';
import Fonts from './Fonts';

View File

@@ -2,9 +2,10 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { i18nextReactInitConfig } from '@brojs/cli';
import App from './app';
import i18next from 'i18next';
import App from './app';
i18next.t = i18next.t.bind(i18next);
const i18nextPromise = i18nextReactInitConfig(i18next);
export default () => <App />;

12
src/lib/date-helpers.ts Normal file
View File

@@ -0,0 +1,12 @@
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,
});
};

View File

@@ -1,35 +0,0 @@
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

@@ -1,10 +1,9 @@
import React, { useState } from 'react';
import React from 'react';
import LayoutArm from '../../components/LayoutArm';
const Page = () => {
const [currentPage, setCurrentPage] = useState('orders');
return <LayoutArm currentPage={currentPage} onSelectPage={setCurrentPage} />;
return <LayoutArm />;
};
export default Page;

View File

@@ -1,5 +1,6 @@
import React, { FC } from 'react';
import { Container, VStack } from '@chakra-ui/react';
import {
BenefitsSection,
Footer,

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { Text, Button, Center, VStack, Heading } from '@chakra-ui/react';
import { Link } from 'react-router-dom';
import { Player } from '@lottiefiles/react-lottie-player';
import i18next from 'i18next';
import animate from '../../assets/animation/notFound.json';
const NotFound = () => {
return (
<Center minH='100vh'>
<VStack spacing={4} textAlign='center'>
<Player
autoplay
loop
src={animate}
style={{
height: '100%',
width: '100%',
maxHeight: '450px',
maxWidth: '450px',
}}
/>
<Heading fontSize='xl'>
{i18next.t(`dry-wash.arm.notFound.title`)}
</Heading>
<Text fontSize='lg'>
{i18next.t(`dry-wash.arm.notFound.description`)}
</Text>
<Button
as={Link}
to='/dry-wash'
colorScheme='teal'
size='lg'
variant='outline'
>
{i18next.t(`dry-wash.arm.notFound.button.back`)}
</Button>
</VStack>
</Center>
);
};
export default NotFound;

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { Button } from '@chakra-ui/react';
import { URLs } from '../../__data__/urls';
import { mockOrder } from '../../mocks/landing';

View File

@@ -1,8 +1,10 @@
import React, { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
import { PageSpinner } from './components';
import Arm from './pages/arm';
import { URLs } from './__data__/urls';
import NotFound from './pages/notFound/notFound';
const Landing = lazy(() => import('./pages/landing'));
const OrderForm = lazy(() => import('./pages/order-form'));
@@ -15,7 +17,8 @@ const Routers = () => {
<Route path={URLs.landing.url} element={<Landing />} />
<Route path={URLs.orderForm.url} element={<OrderForm />} />
<Route path={URLs.orderView.url} element={<OrderView />} />
<Route path='/dry-wash/arm' element={<Arm />}></Route>
<Route path='/dry-wash/arm/*' element={<Arm />}></Route>
<Route path='*' element={<NotFound />} />
</Routes>
</Suspense>
);

View File

@@ -0,0 +1,35 @@
{
"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

@@ -0,0 +1,25 @@
{
"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"
}
]
}