feat: make layout, form, order pages and wizards (#6) #13
1
.gitignore
vendored
1
.gitignore
vendored
@ -130,3 +130,4 @@ dist
|
|||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
.idea
|
8
.prettierrc.json
Normal file
8
.prettierrc.json
Normal 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
1766
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "dry-wash",
|
"name": "dry-wash-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": {
|
||||||
primakov
commented
@types/react @types/react
|
|||||||
|
"@types/react-dom": "^18.3.1",
|
||||||
|
"prettier": "3.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
src/app.tsx
15
src/app.tsx
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
17
src/components /LayoutArm/LayoutArm.tsx
Normal file
17
src/components /LayoutArm/LayoutArm.tsx
Normal 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 />}
|
||||||
primakov
commented
Перевести на router Перевести на router
|
|||||||
|
{currentPage === 'masters' && <Masters />}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default LayoutArm;
|
1
src/components /LayoutArm/index.ts
Normal file
1
src/components /LayoutArm/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './LayoutArm';
|
24
src/components /MasterActionsMenu/MasterActionsMenu.tsx
Normal file
24
src/components /MasterActionsMenu/MasterActionsMenu.tsx
Normal 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;
|
1
src/components /MasterActionsMenu/index.ts
Normal file
1
src/components /MasterActionsMenu/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './MasterActionsMenu';
|
28
src/components /MasterItem/MasterItem.tsx
Normal file
28
src/components /MasterItem/MasterItem.tsx
Normal 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;
|
1
src/components /MasterItem/index.ts
Normal file
1
src/components /MasterItem/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './MasterItem';
|
65
src/components /MasterModal/MasterDrawer.tsx
Normal file
65
src/components /MasterModal/MasterDrawer.tsx
Normal 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;
|
1
src/components /MasterModal/index.ts
Normal file
1
src/components /MasterModal/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './MasterDrawer';
|
50
src/components /Masters/Masters.tsx
Normal file
50
src/components /Masters/Masters.tsx
Normal 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;
|
1
src/components /Masters/index.ts
Normal file
1
src/components /Masters/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './Masters';
|
40
src/components /OrderItem/OrderItem.tsx
Normal file
40
src/components /OrderItem/OrderItem.tsx
Normal 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;
|
1
src/components /OrderItem/index.ts
Normal file
1
src/components /OrderItem/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './OrderItem';
|
38
src/components /Orders/Orders.tsx
Normal file
38
src/components /Orders/Orders.tsx
Normal 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;
|
1
src/components /Orders/index.ts
Normal file
1
src/components /Orders/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './Orders';
|
42
src/components /Sidebar/Sidebar.tsx
Normal file
42
src/components /Sidebar/Sidebar.tsx
Normal 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;
|
1
src/components /Sidebar/index.ts
Normal file
1
src/components /Sidebar/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './Sidebar';
|
@ -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
35
src/mocks /index.ts
Normal 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',
|
||||||
|
},
|
||||||
|
];
|
@ -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;
|
||||||
|
@ -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
5
types.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
declare interface NodeModule {
|
||||||
|
hot?: {
|
||||||
|
accept: (path: string, callback: () => void) => void;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user
Надо бы убрать -pl