Compare commits

..

No commits in common. "c2fad8a3ff379972c8fad87c8eb2a45d08364189" and "a63304b5e4ebc7028ba40a49fa0332d0a8b2bc45" have entirely different histories.

12 changed files with 102 additions and 177 deletions
locales
src
components
ErrorBoundary
MasterActionsMenu
MasterDrawer
Masters
OrderItem
Orders
Sidebar
landing
CtaButton
SiteLogo
pages/arm

@ -13,41 +13,5 @@
"dry-wash.landing.hero-section.headline": "Revitalize Your Ride with Eco-Friendly Care!", "dry-wash.landing.hero-section.headline": "Revitalize Your Ride with Eco-Friendly Care!",
"dry-wash.landing.make-order-button": "Make order", "dry-wash.landing.make-order-button": "Make order",
"dry-wash.landing.site-logo": "The logo of the \"Dry Master\" company", "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"
} }

@ -3,7 +3,7 @@
"dry-wash.arm.order.title": "Заказы", "dry-wash.arm.order.title": "Заказы",
"dry-wash.arm.order.status.progress": "Выполняется", "dry-wash.arm.order.status.progress": "Выполняется",
"dry-wash.arm.order.status.complete": "Завершено", "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.working": "В работе",
"dry-wash.arm.order.status.canceled": "Отменено", "dry-wash.arm.order.status.canceled": "Отменено",
"dry-wash.arm.order.status.placeholder": "Выберите статус", "dry-wash.arm.order.status.placeholder": "Выберите статус",
@ -26,9 +26,9 @@
"dry-wash.arm.master.drawer.inputPhone.placeholder": "Введите номер телефона", "dry-wash.arm.master.drawer.inputPhone.placeholder": "Введите номер телефона",
"dry-wash.arm.master.drawer.button.save": "Сохранить", "dry-wash.arm.master.drawer.button.save": "Сохранить",
"dry-wash.arm.master.drawer.button.cancel": "Отменить", "dry-wash.arm.master.drawer.button.cancel": "Отменить",
"dry-wash.arm.master.sideBar.orders": "Заказы", "dry-wash.arm.master.sideBar.title": " Сухой мастер",
"dry-wash.arm.master.sideBar.master": "Мастера", "dry-wash.arm.master.sideBar.title.master": "Мастера",
"dry-wash.arm.master.sideBar.title": "Сухой мастер", "dry-wash.arm.master.sideBar.title.orders": "Заказы",
"dry-wash.landing.benefits-section.description": "Откройте для себя преимущества наших услуг по химчистке автомобилей", "dry-wash.landing.benefits-section.description": "Откройте для себя преимущества наших услуг по химчистке автомобилей",
"dry-wash.landing.benefits-section.heading": "Преимущества экологичной автомойки", "dry-wash.landing.benefits-section.heading": "Преимущества экологичной автомойки",
"dry-wash.landing.benefits-section.list.0": "Экологически безопасные продукты", "dry-wash.landing.benefits-section.list.0": "Экологически безопасные продукты",
@ -46,8 +46,8 @@
"dry-wash.landing.social-proof-section.heading": "Нас выбирают", "dry-wash.landing.social-proof-section.heading": "Нас выбирают",
"dry-wash.notFound.title": "Страница не найдена", "dry-wash.notFound.title": "Страница не найдена",
"dry-wash.notFound.description": "К сожалению, запрашиваемая вами страница не существует.", "dry-wash.notFound.description": "К сожалению, запрашиваемая вами страница не существует.",
"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": "Перезагрузить страницу"
} }

@ -34,16 +34,15 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
render() { render() {
const { hasError } = this.state; const { hasError } = this.state;
//TODO: добавить анимацию после залива 404 страницы //TODO: добавить анимацию после залива 404 страницы
//TODO: может сделать обертку для хука, чтоб язык менялся без перезагрузки
if (hasError) { if (hasError) {
return ( return (
<Center minH='100vh'> <Center minH='100vh'>
<VStack spacing={4} textAlign='center'> <VStack spacing={4} textAlign='center'>
<Heading as='h1' size='2xl'> <Heading as='h1' size='2xl'>
{i18next.t('~:dry-wash.errorBoundary.title')} {i18next.t('dry-wash.errorBoundary.title')}
</Heading> </Heading>
<Text fontSize='lg'> <Text fontSize='lg'>
{i18next.t('~:dry-wash.errorBoundary.description')} {i18next.t('dry-wash.errorBoundary.description')}
</Text> </Text>
<Button <Button
colorScheme='teal' colorScheme='teal'
@ -51,7 +50,7 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
variant='outline' variant='outline'
onClick={() => window.location.reload()} onClick={() => window.location.reload()}
> >
{i18next.t('~:dry-wash.errorBoundary.button.reload')} {i18next.t('dry-wash.errorBoundary.button.reload')}
</Button> </Button>
</VStack> </VStack>
</Center> </Center>

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

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

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

@ -1,26 +1,8 @@
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 i18next from 'i18next';
const statuses = [ const statuses = ['pending', 'progress', 'working', 'canceled', 'complete'];
'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;
washTime?: string;
orderDate?: string;
status?: GetArrItemType<typeof statuses>;
phone?: string;
location?: string;
};
const OrderItem = ({ const OrderItem = ({
carNumber, carNumber,
@ -29,11 +11,7 @@ const OrderItem = ({
status, status,
phone, phone,
location, location,
}: OrderProps) => { }) => {
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.order',
});
const [statusSelect, setStatus] = useState(status); const [statusSelect, setStatus] = useState(status);
return ( return (
@ -44,12 +22,12 @@ const OrderItem = ({
<Td> <Td>
<Select <Select
value={statusSelect} value={statusSelect}
onChange={(e) => setStatus(e.target.value as OrderProps['status'])} onChange={(e) => setStatus(e.target.value)}
placeholder={t(`status.placeholder`)} placeholder={i18next.t(`dry-wash.arm.order.status.placeholder`)}
> >
{statuses.map((status) => ( {statuses.map((status) => (
<option key={status} value={status}> <option key={status} value={status}>
{t(`status.${status}`)} {i18next.t(`dry-wash.arm.order.status.${status}`)}
</option> </option>
))} ))}
</Select> </Select>

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

@ -1,14 +1,10 @@
import { Box, Button, Heading, VStack, Divider } from '@chakra-ui/react'; import { Box, Button, Heading, VStack } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { Divider } from '@chakra-ui/react';
import i18next from 'i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
const Sidebar = () => { const Sidebar = () => (
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.master.sideBar',
});
return (
<Box <Box
borderRight='1px solid black' borderRight='1px solid black'
bg='gray.50' bg='gray.50'
@ -18,7 +14,7 @@ const Sidebar = () => {
pt='8' pt='8'
> >
<Heading color='green' size='lg' mb='5'> <Heading color='green' size='lg' mb='5'>
{t('title')} {i18next.t(`dry-wash.arm.master.sideBar.title`)}
</Heading> </Heading>
<VStack align='start' spacing='4'> <VStack align='start' spacing='4'>
@ -30,7 +26,7 @@ const Sidebar = () => {
colorScheme='green' colorScheme='green'
variant='ghost' variant='ghost'
> >
{t('orders')} {i18next.t(`dry-wash.arm.master.sideBar.title.orders`)}
</Button> </Button>
<Divider /> <Divider />
<Button <Button
@ -40,12 +36,11 @@ const Sidebar = () => {
colorScheme='green' colorScheme='green'
variant='ghost' variant='ghost'
> >
{t('master')} {i18next.t(`dry-wash.arm.master.sideBar.title.master`)}
</Button> </Button>
<Divider /> <Divider />
</VStack> </VStack>
</Box> </Box>
); );
};
export default Sidebar; export default Sidebar;

@ -15,7 +15,7 @@ export const CtaButton: FC<ButtonProps> = (props) => {
colorScheme='primary' colorScheme='primary'
{...props} {...props}
> >
{t('~:dry-wash.landing.make-order-button')} {t('dry-wash.landing.make-order-button')}
</Button> </Button>
); );
}; };

@ -7,7 +7,7 @@ import { LogoSvg } from '../../../assets/icons';
export const SiteLogo: FC = () => { export const SiteLogo: FC = () => {
const { t } = useTranslation(); 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 // todo: replace Image by SVG React component

@ -1,4 +1,4 @@
import React from 'react'; import React, { useState } from 'react';
import LayoutArm from '../../components/LayoutArm'; import LayoutArm from '../../components/LayoutArm';