feat: make layout, form, order pages and wizards (#6) #13

Merged
237x237 merged 2 commits from feature/arm-layout into main 2024-11-03 11:44:24 +03:00
26 changed files with 2173 additions and 31 deletions

1
.gitignore vendored
View File

@ -130,3 +130,4 @@ dist
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
.idea

8
.prettierrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"singleQuote": true,
"jsxSingleQuote": true,
"arrowParens": "always",
"max-len": ["error", 140, 2],
"tabWidth": 2,
"useTabs": false
}

1766
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{ {
"name": "dry-wash", "name": "dry-wash-pl",
Review

Надо бы убрать -pl

Надо бы убрать _-pl_
"version": "0.0.0", "version": "0.0.0",
"description": "<a id=\"readme-top\"></a>", "description": "<a id=\"readme-top\"></a>",
"main": "./src/index.tsx", "main": "./src/index.tsx",
@ -15,9 +15,19 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@brojs/cli": "^1.3.0", "@brojs/cli": "^1.3.0",
"@chakra-ui/icons": "^2.2.4",
"@chakra-ui/react": "^2.4.2",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@types/react": "^18.3.12",
"express": "^4.21.1", "express": "^4.21.1",
"framer-motion": "^6.2.8",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-router-dom": "^6.27.0" "react-router-dom": "^6.27.0"
},
"devDependencies": {
Review

@types/react

@types/react
"@types/react-dom": "^18.3.1",
"prettier": "3.3.3"
} }
} }

View File

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

View File

@ -0,0 +1,17 @@
import { Box, Flex } from '@chakra-ui/react';
import Sidebar from '../Sidebar';
import Orders from '../Orders';
import Masters from '../Masters';
import React from 'react';
const LayoutArm = ({ currentPage, onSelectPage }) => (
<Flex h='100vh'>
<Sidebar onSelectPage={onSelectPage} />
<Box flex='1' bg='gray.50'>
{currentPage === 'orders' && <Orders />}
Review

Перевести на router

Перевести на router
{currentPage === 'masters' && <Masters />}
</Box>
</Flex>
);
export default LayoutArm;

View File

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

View File

@ -0,0 +1,24 @@
import React from 'react';
import {
Menu,
MenuButton,
MenuList,
MenuItem,
IconButton,
} from '@chakra-ui/react';
import { EditIcon } from '@chakra-ui/icons';
const MasterActionsMenu = () => {
return (
<Menu>
<MenuButton icon={<EditIcon />} as={IconButton} variant='outline' />
<MenuList>
<MenuItem>Посмотреть профиль</MenuItem>
<MenuItem>Изменить расписание</MenuItem>
<MenuItem>Удалить мастера</MenuItem>
</MenuList>
</Menu>
);
};
export default MasterActionsMenu;

View File

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

View File

@ -0,0 +1,28 @@
import React from 'react';
import { Badge, Link, Stack, Td, Tr } from '@chakra-ui/react';
import MasterActionsMenu from '../MasterActionsMenu';
const MasterItem = ({ name, schedule, phone }) => {
return (
<Tr>
<Td>{name}</Td>
<Td>
<Stack direction='row'>
{schedule.map((time, index) => (
<Badge colorScheme={'green'} key={index}>
{time}
</Badge>
))}
</Stack>
</Td>
<Td>
<Link href='tel:'>{phone}</Link>
</Td>
<Td>
<MasterActionsMenu />
</Td>
</Tr>
);
};
export default MasterItem;

View File

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

View File

@ -0,0 +1,65 @@
import React, { useState } from 'react';
import {
Button,
FormControl,
FormLabel,
Input,
Drawer,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
} from '@chakra-ui/react';
const MasterDrawer = ({ isOpen, onClose }) => {
const [newMaster, setNewMaster] = useState({ name: '', phone: '' });
const handleSave = () => {
console.log(`Сохранение мастера: ${newMaster}`);
onClose();
};
return (
<Drawer isOpen={isOpen} onClose={onClose} size='md'>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>Добавить нового мастера</DrawerHeader>
<DrawerBody>
<FormControl mb='4'>
<FormLabel>ФИО</FormLabel>
<Input
value={newMaster.name}
onChange={(e) =>
setNewMaster({ ...newMaster, name: e.target.value })
}
placeholder='Введите ФИО'
/>
</FormControl>
<FormControl>
<FormLabel>Номер телефона</FormLabel>
<Input
value={newMaster.phone}
onChange={(e) =>
setNewMaster({ ...newMaster, phone: e.target.value })
}
placeholder='Введите номер телефона'
/>
</FormControl>
</DrawerBody>
<DrawerFooter>
<Button colorScheme='teal' mr={3} onClick={handleSave}>
Сохранить
</Button>
<Button variant='ghost' onClick={onClose}>
Отменить
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
};
export default MasterDrawer;

View File

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

View File

@ -0,0 +1,50 @@
import React from 'react';
import {
Box,
Heading,
Table,
Thead,
Tbody,
Tr,
Th,
Button,
useDisclosure,
Flex,
} from '@chakra-ui/react';
import { mastersData } from '../../mocks ';
import MasterItem from '../MasterItem';
import MasterDrawer from '../MasterModal';
const TABLE_HEADERS = ['Имя', 'Актуальная занятость', 'Телефон', 'Действия'];
const Masters = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<Box p='8'>
<Flex justifyContent='space-between' alignItems='center' mb='5'>
<Heading size='lg'>Мастера</Heading>
<Button colorScheme='green' onClick={onOpen}>
+ Добавить
</Button>
</Flex>
<Table variant='simple' colorScheme='blackAlpha'>
<Thead>
<Tr>
{TABLE_HEADERS.map((name) => (
<Th>{name}</Th>
))}
</Tr>
</Thead>
<Tbody>
{mastersData.map((master, index) => (
<MasterItem key={index} {...master} />
))}
</Tbody>
</Table>
<MasterDrawer isOpen={isOpen} onClose={onClose} />
</Box>
);
};
export default Masters;

View File

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

View File

@ -0,0 +1,40 @@
import React, { useState } from 'react';
import { Td, Tr, Link, Select } from '@chakra-ui/react';
const OrderItem = ({
carNumber,
washTime,
orderDate,
status,
phone,
location,
}) => {
const [statusSelect, setStatus] = useState(status);
return (
<Tr>
<Td>{carNumber}</Td>
<Td>{washTime}</Td>
<Td>{orderDate}</Td>
<Td>
<Select
value={statusSelect}
onChange={(e) => setStatus(e.target.value)}
placeholder='Выберите статус'
>
<option value='в ожидании'>в ожидании</option>
<option value='В процессе'>в процессе</option>
<option value='в работе'>в работе</option>
<option value='отменил'>отменил</option>
<option value='Завершено'>Завершено</option>
</Select>
</Td>
<Td>
<Link href='tel:'>{phone}</Link>
</Td>
<Td>{location}</Td>
</Tr>
);
};
export default OrderItem;

View File

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

View File

@ -0,0 +1,38 @@
import { Box, Heading, Table, Thead, Tbody, Tr, Th } from '@chakra-ui/react';
import React from 'react';
import { ordersData } from '../../mocks ';
import OrderItem from '../OrderItem';
const Orders = () => {
const TABLE_HEADERS = [
'Номер машины',
'Время мойки',
'Дата заказа',
'Статус',
'Телефон',
'Расположение',
];
return (
<Box p='8'>
<Heading size='lg' mb='5'>
Заказы
</Heading>
<Table variant='simple' colorScheme='blackAlpha'>
<Thead>
<Tr>
{TABLE_HEADERS.map((name, key) => (
<Th key={key}>{name}</Th>
))}
</Tr>
</Thead>
<Tbody>
{ordersData.map((order, index) => (
<OrderItem key={index} {...order} />
))}
</Tbody>
</Table>
</Box>
);
};
export default Orders;

View File

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

View File

@ -0,0 +1,42 @@
import { Box, Button, Heading, VStack } from '@chakra-ui/react';
import React from 'react';
import { Divider } from '@chakra-ui/react';
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'>
Сухой мастер
</Heading>
<VStack align='start' spacing='4'>
<Divider />
<Button
onClick={() => onSelectPage('orders')}
w='100%'
colorScheme='green'
variant='ghost'
>
Заказы
</Button>
<Divider />
<Button
onClick={() => onSelectPage('masters')}
w='100%'
colorScheme='green'
variant='ghost'
>
Мастера
</Button>
<Divider />
</VStack>
</Box>
);
export default Sidebar;

View File

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

View File

@ -5,18 +5,17 @@ import ReactDOM from 'react-dom/client';
import App from './app'; import App from './app';
export default () => <App/>; export default () => <App />;
let rootElement: ReactDOM.Root let rootElement: ReactDOM.Root;
export const mount = (Сomponent, element = document.getElementById('app')) => { export const mount = (Component, element = document.getElementById('app')) => {
const rootElement = ReactDOM.createRoot(element); const rootElement = ReactDOM.createRoot(element);
rootElement.render(<Сomponent/>); rootElement.render(<Component />);
if (module.hot) {
if(module.hot) { module.hot.accept('./app', () => {
module.hot.accept('./app', ()=> { rootElement.render(<Component />);
rootElement.render(<Сomponent/>); });
})
} }
}; };

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

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

View File

@ -1,15 +1,15 @@
import React from "react"; import React from 'react';
import { Routes, Route } from "react-router-dom"; import { Routes, Route } from 'react-router-dom';
import Arm from "./pages/arm"; import Arm from './pages/arm';
import Order from "./pages/order-view"; import Order from './pages/order-view';
import Landing from "./pages/landing"; import Landing from './pages/landing';
const Routers = () => { const Routers = () => {
return ( return (
<Routes> <Routes>
<Route path="/dry-wash-pl" element={<Landing />}></Route> <Route path='/dry-wash' element={<Landing />}></Route>
<Route path="/dry-wash-pl/order" element={<Order />}></Route> <Route path='/dry-wash/order' element={<Order />}></Route>
<Route path="/dry-wash-pl/arm" element={<Arm />}></Route> <Route path='/dry-wash/arm' element={<Arm />}></Route>
</Routes> </Routes>
); );
}; };

5
types.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare interface NodeModule {
hot?: {
accept: (path: string, callback: () => void) => void;
};
}