Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3382ae3ada | ||
|
|
5ed023866e | ||
|
|
e73773f359 | ||
|
|
a9a9b3cadd | ||
| 06abc15c9a | |||
| 6ca7de9467 | |||
| 939f107d1c | |||
| 4f92125e6d | |||
| 3ea501161c |
@@ -73,6 +73,10 @@
|
||||
"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.schedule.empty": "free",
|
||||
"dry-wash.arm.master.editable.aria.cancel": "Undo changes",
|
||||
"dry-wash.arm.master.editable.aria.save": "Save changes ",
|
||||
"dry-wash.arm.master.editable.aria.edit": "Edit",
|
||||
"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",
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
"dry-wash.arm.master.table.header.phone": "Телефон",
|
||||
"dry-wash.arm.master.table.header.actions": "Действия",
|
||||
"dry-wash.arm.master.table.actionsMenu.delete": "Удалить мастера",
|
||||
"dry-wash.arm.master.schedule.empty": "Свободен",
|
||||
"dry-wash.arm.master.editable.aria.cancel": "Отменить изменения",
|
||||
"dry-wash.arm.master.editable.aria.save": "Сохранить изменения",
|
||||
"dry-wash.arm.master.editable.aria.edit": "Редактировать",
|
||||
"dry-wash.arm.master.drawer.title": "Добавить нового мастера",
|
||||
"dry-wash.arm.master.drawer.inputName.label": "ФИО",
|
||||
"dry-wash.arm.master.drawer.inputName.placeholder": "Введите ФИО",
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "dry-wash",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "dry-wash",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@brojs/cli": "^1.6.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dry-wash",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"description": "<a id=\"readme-top\"></a>",
|
||||
"main": "./src/index.tsx",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getConfigValue } from '@brojs/cli';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
enum ArmEndpoints {
|
||||
ORDERS = '/arm/orders',
|
||||
@@ -9,12 +10,15 @@ const armService = () => {
|
||||
const endpoint = getConfigValue('dry-wash.api');
|
||||
|
||||
const fetchOrders = async ({ date }: { date: Date }) => {
|
||||
const startDate = dayjs(date).startOf('day').toISOString();
|
||||
const endDate = dayjs(date).endOf('day').toISOString();
|
||||
|
||||
const response = await fetch(`${endpoint}${ArmEndpoints.ORDERS}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ date }),
|
||||
body: JSON.stringify({ startDate, endDate }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -68,7 +72,33 @@ const armService = () => {
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
return { fetchOrders, fetchMasters, addMaster, deleteMaster };
|
||||
const updateMaster = async ({
|
||||
id,
|
||||
name,
|
||||
phone,
|
||||
}: {
|
||||
id: string;
|
||||
name?: string;
|
||||
phone?: string;
|
||||
}) => {
|
||||
const body = JSON.stringify({ name, phone });
|
||||
|
||||
const response = await fetch(`${endpoint}${ArmEndpoints.MASTERS}/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch update masters: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
return { fetchOrders, fetchMasters, addMaster, deleteMaster, updateMaster };
|
||||
};
|
||||
|
||||
export { armService, ArmEndpoints };
|
||||
|
||||
119
src/components/Editable/Editable.tsx
Normal file
119
src/components/Editable/Editable.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Editable,
|
||||
EditableInput,
|
||||
EditablePreview,
|
||||
Flex,
|
||||
IconButton,
|
||||
Input,
|
||||
useEditableControls,
|
||||
ButtonGroup,
|
||||
Stack,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
import { CheckIcon, CloseIcon, EditIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface EditableWrapperProps {
|
||||
value: string;
|
||||
onSubmit: ({
|
||||
id,
|
||||
name,
|
||||
phone,
|
||||
}: {
|
||||
id: string;
|
||||
name?: string;
|
||||
phone?: string;
|
||||
}) => Promise<unknown>;
|
||||
as: 'phone' | 'name';
|
||||
id: string;
|
||||
}
|
||||
|
||||
const EditableWrapper = ({ value, onSubmit, as, id }: EditableWrapperProps) => {
|
||||
const { t } = useTranslation('~', {
|
||||
keyPrefix: 'dry-wash.arm.master.editable',
|
||||
});
|
||||
|
||||
const toast = useToast();
|
||||
const [currentValue, setCurrentValue] = useState<string>(value);
|
||||
|
||||
const handleSubmit = async (newValue: string) => {
|
||||
if (currentValue === newValue) return;
|
||||
|
||||
try {
|
||||
await onSubmit({ id, [as]: newValue });
|
||||
|
||||
setCurrentValue(newValue);
|
||||
|
||||
toast({
|
||||
title: 'Успешно!',
|
||||
description: 'Данные обновлены.',
|
||||
status: 'success',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
position: 'top-right',
|
||||
});
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: 'Ошибка!',
|
||||
description: 'Не удалось обновить данные.',
|
||||
status: 'error',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
position: 'top-right',
|
||||
});
|
||||
console.error('Ошибка при обновлении данных:', error);
|
||||
}
|
||||
};
|
||||
|
||||
function EditableControls() {
|
||||
const {
|
||||
isEditing,
|
||||
getSubmitButtonProps,
|
||||
getCancelButtonProps,
|
||||
getEditButtonProps,
|
||||
} = useEditableControls();
|
||||
|
||||
return isEditing ? (
|
||||
<ButtonGroup justifyContent='center' size='sm'>
|
||||
<IconButton
|
||||
aria-label={t('aria.save')}
|
||||
icon={<CheckIcon />}
|
||||
{...getSubmitButtonProps()}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={t('aria.cancel')}
|
||||
icon={<CloseIcon />}
|
||||
{...getCancelButtonProps()}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
) : (
|
||||
<Flex justifyContent='center'>
|
||||
<IconButton
|
||||
aria-label={t('aria.edit')}
|
||||
size='sm'
|
||||
icon={<EditIcon />}
|
||||
{...getEditButtonProps()}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Editable
|
||||
textAlign='center'
|
||||
defaultValue={currentValue}
|
||||
fontSize='2xl'
|
||||
isPreviewFocusable={false}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Stack direction={['column', 'row']} spacing='15px'>
|
||||
<EditablePreview />
|
||||
<Input as={EditableInput} />
|
||||
<EditableControls />
|
||||
</Stack>
|
||||
</Editable>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditableWrapper;
|
||||
@@ -1,8 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Badge, Link, Stack, Td, Tr } from '@chakra-ui/react';
|
||||
import { Badge, Stack, Td, Tr, Text } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import MasterActionsMenu from '../MasterActionsMenu';
|
||||
import { getTimeSlot } from '../../lib';
|
||||
import EditableWrapper from '../Editable/Editable';
|
||||
import { armService } from '../../api/arm';
|
||||
|
||||
export interface Schedule {
|
||||
id: string;
|
||||
@@ -18,20 +21,41 @@ export type MasterProps = {
|
||||
};
|
||||
|
||||
const MasterItem = ({ name, phone, id, schedule }) => {
|
||||
const { updateMaster } = armService();
|
||||
const { t } = useTranslation('~', {
|
||||
keyPrefix: 'dry-wash.arm.master',
|
||||
});
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
<Td>{name}</Td>
|
||||
<Td>
|
||||
<Stack direction='row'>
|
||||
{schedule.map(({ startWashTime, endWashTime }, index) => (
|
||||
<Badge colorScheme={'green'} key={index}>
|
||||
{getTimeSlot(startWashTime, endWashTime)}
|
||||
</Badge>
|
||||
))}
|
||||
</Stack>
|
||||
<EditableWrapper
|
||||
id={id}
|
||||
as={'name'}
|
||||
value={name}
|
||||
onSubmit={updateMaster}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Link href='tel:'>{phone}</Link>
|
||||
{schedule?.length > 0 ? (
|
||||
<Stack direction='row'>
|
||||
{schedule?.map(({ startWashTime, endWashTime }, index: number) => (
|
||||
<Badge colorScheme={'green'} key={index}>
|
||||
{getTimeSlot(startWashTime, endWashTime)}
|
||||
</Badge>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<Text color='gray.500'>{t('schedule.empty')}</Text>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<EditableWrapper
|
||||
id={id}
|
||||
as={'phone'}
|
||||
value={phone}
|
||||
onSubmit={updateMaster}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<MasterActionsMenu id={id} />
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getConfigValue } from '@brojs/cli';
|
||||
import { InputProps, SelectProps } from "@chakra-ui/react";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { Order } from "../../../models/landing";
|
||||
|
||||
@@ -50,17 +52,28 @@ export const formatFormValues = ({ phone, carNumber, carBody, carColor, carLocat
|
||||
},
|
||||
washing: {
|
||||
location: carLocation,
|
||||
begin: availableDatetimeBegin,
|
||||
end: availableDatetimeEnd,
|
||||
begin: dayjs(availableDatetimeBegin).toISOString(),
|
||||
end: dayjs(availableDatetimeEnd).toISOString(),
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const onSubmit = (values: OrderFormValues) => {
|
||||
return new Promise((resolve) => {
|
||||
console.log(formatFormValues(values));
|
||||
resolve(formatFormValues(values));
|
||||
const endpoint = getConfigValue('dry-wash.api');
|
||||
|
||||
export const onSubmit = async (values: OrderFormValues) => {
|
||||
const response = await fetch(`${endpoint}/order/create`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formatFormValues(values)),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to create order: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
export const inputCommonStyles: Partial<InputProps & SelectProps> = {
|
||||
|
||||
@@ -76,6 +76,12 @@ router.get('/order/:orderId', ({ params }, res) => {
|
||||
);
|
||||
});
|
||||
|
||||
router.post('/order/create', (req, res) => {
|
||||
res
|
||||
.status(200)
|
||||
.send({ success: true, body: { ok: true } });
|
||||
});
|
||||
|
||||
router.use('/admin', require('./admin'));
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -2,22 +2,12 @@
|
||||
"success": true,
|
||||
"body": [
|
||||
{
|
||||
"id": "masters1",
|
||||
"id": "4545423234",
|
||||
"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",
|
||||
"id": "345354234",
|
||||
"name": "Иван Иванов",
|
||||
"schedule": [ {
|
||||
"id": "order1",
|
||||
|
||||
Reference in New Issue
Block a user