feat: add RTK for master
All checks were successful
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
All checks were successful
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
This commit is contained in:
parent
adce5392a1
commit
d15bd6f7d2
87
package-lock.json
generated
87
package-lock.json
generated
@ -17,6 +17,7 @@
|
|||||||
"@fontsource/open-sans": "^5.1.0",
|
"@fontsource/open-sans": "^5.1.0",
|
||||||
"@lottiefiles/react-lottie-player": "^3.5.4",
|
"@lottiefiles/react-lottie-player": "^3.5.4",
|
||||||
"@pbe/react-yandex-maps": "^1.2.5",
|
"@pbe/react-yandex-maps": "^1.2.5",
|
||||||
|
"@reduxjs/toolkit": "^2.5.0",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
@ -29,6 +30,7 @@
|
|||||||
"react-i18next": "^15.1.1",
|
"react-i18next": "^15.1.1",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
"react-phone-number-input": "^3.4.9",
|
"react-phone-number-input": "^3.4.9",
|
||||||
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^6.27.0"
|
"react-router-dom": "^6.27.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -2639,6 +2641,38 @@
|
|||||||
"url": "https://opencollective.com/popperjs"
|
"url": "https://opencollective.com/popperjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-awNe2oTodsZ6LmRqmkFhtb/KH03hUhxOamEQy411m3Njj3BbFvoBovxo4Q1cBWnV1ErprVj9MlF0UPXkng0eyg==",
|
||||||
|
"dependencies": {
|
||||||
|
"immer": "^10.0.3",
|
||||||
|
"redux": "^5.0.1",
|
||||||
|
"redux-thunk": "^3.1.0",
|
||||||
|
"reselect": "^5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
||||||
|
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit/node_modules/immer": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@remix-run/router": {
|
"node_modules/@remix-run/router": {
|
||||||
"version": "1.20.0",
|
"version": "1.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz",
|
||||||
@ -2802,6 +2836,11 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/use-sync-external-store": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
|
||||||
|
},
|
||||||
"node_modules/@types/yandex-maps": {
|
"node_modules/@types/yandex-maps": {
|
||||||
"version": "2.1.29",
|
"version": "2.1.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yandex-maps/-/yandex-maps-2.1.29.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yandex-maps/-/yandex-maps-2.1.29.tgz",
|
||||||
@ -8735,6 +8774,28 @@
|
|||||||
"react-dom": ">=16.8"
|
"react-dom": ">=16.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-redux": {
|
||||||
|
"version": "9.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||||
|
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/use-sync-external-store": "^0.0.6",
|
||||||
|
"use-sync-external-store": "^1.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^18.2.25 || ^19",
|
||||||
|
"react": "^18.0 || ^19",
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-remove-scroll": {
|
"node_modules/react-remove-scroll": {
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz",
|
||||||
@ -8892,6 +8953,19 @@
|
|||||||
"recursive-watch": "bin.js"
|
"recursive-watch": "bin.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redux": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
|
||||||
|
},
|
||||||
|
"node_modules/redux-thunk": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
|
||||||
@ -9009,6 +9083,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reselect": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
@ -10249,6 +10328,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
"@fontsource/open-sans": "^5.1.0",
|
"@fontsource/open-sans": "^5.1.0",
|
||||||
"@lottiefiles/react-lottie-player": "^3.5.4",
|
"@lottiefiles/react-lottie-player": "^3.5.4",
|
||||||
"@pbe/react-yandex-maps": "^1.2.5",
|
"@pbe/react-yandex-maps": "^1.2.5",
|
||||||
|
"@reduxjs/toolkit": "^2.5.0",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
@ -37,6 +38,7 @@
|
|||||||
"react-i18next": "^15.1.1",
|
"react-i18next": "^15.1.1",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
"react-phone-number-input": "^3.4.9",
|
"react-phone-number-input": "^3.4.9",
|
||||||
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^6.27.0"
|
"react-router-dom": "^6.27.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
41
src/__data__/service/api.ts
Normal file
41
src/__data__/service/api.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
||||||
|
import { getConfigValue } from '@brojs/cli';
|
||||||
|
|
||||||
|
import { Master } from '../../models/api/master';
|
||||||
|
|
||||||
|
type SuccessResponse<Body> = {
|
||||||
|
success: true;
|
||||||
|
body: Body;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ErrorResponse = {
|
||||||
|
success: false;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BaseResponse<Body> = SuccessResponse<Body> | ErrorResponse;
|
||||||
|
|
||||||
|
export const api = createApi({
|
||||||
|
reducerPath: 'api',
|
||||||
|
baseQuery: fetchBaseQuery({ baseUrl: getConfigValue('dry-wash.api') }),
|
||||||
|
tagTypes: ['Masters'],
|
||||||
|
endpoints: (builder) => ({
|
||||||
|
getMasters: builder.query<Master[], void>({
|
||||||
|
query: () => ({ url: '/arm/masters' }),
|
||||||
|
transformResponse: (response: BaseResponse<Master[]>) => {
|
||||||
|
if (response.success) {
|
||||||
|
return response.body;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
providesTags: ['Masters'],
|
||||||
|
}),
|
||||||
|
addMaster: builder.mutation<void, Pick<Master, 'name' | 'phone'>>({
|
||||||
|
query: (master) => ({
|
||||||
|
url: '/arm/masters',
|
||||||
|
method: 'POST',
|
||||||
|
body: master,
|
||||||
|
}),
|
||||||
|
invalidatesTags: ['Masters'],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
14
src/__data__/store.ts
Normal file
14
src/__data__/store.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import { api } from './service/api';
|
||||||
|
|
||||||
|
export const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
[api.reducerPath]: api.reducer,
|
||||||
|
},
|
||||||
|
middleware: (getDefaultMiddleware) =>
|
||||||
|
getDefaultMiddleware().concat(api.middleware),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
|
export type AppDispatch = typeof store.dispatch;
|
18
src/app.tsx
18
src/app.tsx
@ -1,19 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { ChakraProvider, theme as chakraTheme } from '@chakra-ui/react';
|
import { ChakraProvider, theme as chakraTheme } from '@chakra-ui/react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import Routers from './routes';
|
import Routers from './routes';
|
||||||
import ErrorBoundary from './components/ErrorBoundary';
|
import ErrorBoundary from './components/ErrorBoundary';
|
||||||
|
import { store } from './__data__/store';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<ChakraProvider theme={chakraTheme}>
|
<Provider store={store}>
|
||||||
<ErrorBoundary>
|
<ChakraProvider theme={chakraTheme}>
|
||||||
<BrowserRouter>
|
<ErrorBoundary>
|
||||||
<Routers />
|
<BrowserRouter>
|
||||||
</BrowserRouter>
|
<Routers />
|
||||||
</ErrorBoundary>
|
</BrowserRouter>
|
||||||
</ChakraProvider>
|
</ErrorBoundary>
|
||||||
|
</ChakraProvider>
|
||||||
|
</Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -18,21 +18,29 @@ import {
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PhoneIcon } from '@chakra-ui/icons';
|
import { PhoneIcon } from '@chakra-ui/icons';
|
||||||
|
|
||||||
import { armService } from '../../api/arm';
|
import { api } from '../../__data__/service/api';
|
||||||
|
|
||||||
const MasterDrawer = ({ isOpen, onClose }) => {
|
const MasterDrawer = ({ isOpen, onClose }) => {
|
||||||
const { addMaster } = armService();
|
const [addMaster, { error, isSuccess }] = api.useAddMasterMutation();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
const [newMaster, setNewMaster] = useState({ name: '', phone: '' });
|
const [newMaster, setNewMaster] = useState({ name: '', phone: '' });
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (newMaster.name.trim() === '' || newMaster.phone.trim() === '') {
|
const trimMaster = {
|
||||||
|
phone: newMaster.phone.trim(),
|
||||||
|
name: newMaster.name.trim(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (trimMaster.name === '' || trimMaster.phone === '') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
addMaster(trimMaster);
|
||||||
await addMaster(newMaster);
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSuccess) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Мастер создан.',
|
title: 'Мастер создан.',
|
||||||
description: `Мастер "${newMaster.name}" успешно добавлен.`,
|
description: `Мастер "${newMaster.name}" успешно добавлен.`,
|
||||||
@ -42,7 +50,11 @@ const MasterDrawer = ({ isOpen, onClose }) => {
|
|||||||
position: 'top-right',
|
position: 'top-right',
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
} catch (error) {
|
}
|
||||||
|
}, [isSuccess]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (error) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Ошибка при создании мастера.',
|
title: 'Ошибка при создании мастера.',
|
||||||
description: 'Не удалось добавить мастера. Попробуйте еще раз.',
|
description: 'Не удалось добавить мастера. Попробуйте еще раз.',
|
||||||
@ -53,7 +65,7 @@ const MasterDrawer = ({ isOpen, onClose }) => {
|
|||||||
});
|
});
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
};
|
}, [error]);
|
||||||
|
|
||||||
const { t } = useTranslation('~', {
|
const { t } = useTranslation('~', {
|
||||||
keyPrefix: 'dry-wash.arm.master.drawer',
|
keyPrefix: 'dry-wash.arm.master.drawer',
|
||||||
|
@ -7,19 +7,6 @@ import { getTimeSlot } from '../../lib';
|
|||||||
import EditableWrapper from '../Editable/Editable';
|
import EditableWrapper from '../Editable/Editable';
|
||||||
import { armService } from '../../api/arm';
|
import { armService } from '../../api/arm';
|
||||||
|
|
||||||
export interface Schedule {
|
|
||||||
id: string;
|
|
||||||
startWashTime: string;
|
|
||||||
endWashTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MasterProps = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
phone: string;
|
|
||||||
schedule: Schedule[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const MasterItem = ({ name, phone, id, schedule }) => {
|
const MasterItem = ({ name, phone, id, schedule }) => {
|
||||||
const { updateMaster } = armService();
|
const { updateMaster } = armService();
|
||||||
const { t } = useTranslation('~', {
|
const { t } = useTranslation('~', {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Heading,
|
Heading,
|
||||||
@ -19,8 +19,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import MasterItem from '../MasterItem';
|
import MasterItem from '../MasterItem';
|
||||||
import MasterDrawer from '../MasterDrawer';
|
import MasterDrawer from '../MasterDrawer';
|
||||||
import { armService } from '../../api/arm';
|
import { api } from '../../__data__/service/api';
|
||||||
import { MasterProps } from '../MasterItem/MasterItem';
|
|
||||||
|
|
||||||
const TABLE_HEADERS = [
|
const TABLE_HEADERS = [
|
||||||
'name' as const,
|
'name' as const,
|
||||||
@ -36,35 +35,23 @@ const Masters = () => {
|
|||||||
keyPrefix: 'dry-wash.arm.master',
|
keyPrefix: 'dry-wash.arm.master',
|
||||||
});
|
});
|
||||||
|
|
||||||
const [masters, setMasters] = useState<MasterProps[]>([]);
|
const {
|
||||||
const [loading, setLoading] = useState(false);
|
data: masters,
|
||||||
const [error, setError] = useState<string | null>(null);
|
error,
|
||||||
|
isLoading,
|
||||||
const { fetchMasters } = armService();
|
isSuccess,
|
||||||
|
} = api.useGetMastersQuery();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadMasters = async () => {
|
if (error) {
|
||||||
setLoading(true);
|
toast({
|
||||||
|
title: t('error.title'),
|
||||||
try {
|
status: 'error',
|
||||||
const data = await fetchMasters();
|
isClosable: true,
|
||||||
setMasters(data.body);
|
position: 'bottom-right',
|
||||||
} catch (err) {
|
});
|
||||||
setError(err.message);
|
}
|
||||||
toast({
|
}, [error]);
|
||||||
title: t('error.title'),
|
|
||||||
status: 'error',
|
|
||||||
duration: 5000,
|
|
||||||
isClosable: true,
|
|
||||||
position: 'bottom-right',
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadMasters();
|
|
||||||
}, [toast, t]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box p='8'>
|
<Box p='8'>
|
||||||
@ -83,22 +70,21 @@ const Masters = () => {
|
|||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
<Tbody>
|
||||||
{loading && (
|
{isLoading && (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td colSpan={TABLE_HEADERS.length} textAlign='center' py='8'>
|
<Td colSpan={TABLE_HEADERS.length} textAlign='center' py='8'>
|
||||||
<Spinner size='lg' />
|
<Spinner size='lg' />
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
)}
|
)}
|
||||||
{!loading && masters.length === 0 && !error && (
|
{isSuccess && masters.length === 0 && (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td colSpan={TABLE_HEADERS.length}>
|
<Td colSpan={TABLE_HEADERS.length}>
|
||||||
<Text>{t('table.empty')}</Text>
|
<Text>{t('table.empty')}</Text>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
)}
|
)}
|
||||||
{!loading &&
|
{isSuccess &&
|
||||||
!error &&
|
|
||||||
masters.map((master, index) => (
|
masters.map((master, index) => (
|
||||||
<MasterItem key={index} {...master} />
|
<MasterItem key={index} {...master} />
|
||||||
))}
|
))}
|
||||||
|
@ -3,8 +3,8 @@ import { Td, Tr, Link, Select } from '@chakra-ui/react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import { MasterProps } from '../MasterItem/MasterItem';
|
|
||||||
import { getTimeSlot } from '../../lib';
|
import { getTimeSlot } from '../../lib';
|
||||||
|
import { Master } from '../../models/api/master';
|
||||||
import { armService } from '../../api/arm';
|
import { armService } from '../../api/arm';
|
||||||
|
|
||||||
const statuses = [
|
const statuses = [
|
||||||
@ -26,9 +26,9 @@ export type OrderProps = {
|
|||||||
status?: GetArrItemType<typeof statuses>;
|
status?: GetArrItemType<typeof statuses>;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
location?: string;
|
location?: string;
|
||||||
master: MasterProps;
|
master: Master;
|
||||||
notes: '';
|
notes: '';
|
||||||
allMasters: MasterProps[];
|
allMasters: Master[];
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import OrderItem from '../OrderItem';
|
|||||||
import { OrderProps } from '../OrderItem/OrderItem';
|
import { OrderProps } from '../OrderItem/OrderItem';
|
||||||
import { armService } from '../../api/arm';
|
import { armService } from '../../api/arm';
|
||||||
import DateNavigator from '../DateNavigator';
|
import DateNavigator from '../DateNavigator';
|
||||||
import { MasterProps } from '../MasterItem/MasterItem';
|
import { Master } from '../../models/api/master';
|
||||||
|
|
||||||
const TABLE_HEADERS = [
|
const TABLE_HEADERS = [
|
||||||
'carNumber' as const,
|
'carNumber' as const,
|
||||||
@ -41,7 +41,7 @@ const Orders = () => {
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
const [orders, setOrders] = useState<OrderProps[]>([]);
|
const [orders, setOrders] = useState<OrderProps[]>([]);
|
||||||
const [allMasters, setAllMasters] = useState<MasterProps[]>([]);
|
const [allMasters, setAllMasters] = useState<Master[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [currentDate, setCurrentDate] = useState(new Date());
|
const [currentDate, setCurrentDate] = useState(new Date());
|
||||||
|
12
src/models/api/master.ts
Normal file
12
src/models/api/master.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export interface Schedule {
|
||||||
|
id: string;
|
||||||
|
startWashTime: string;
|
||||||
|
endWashTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Master = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
phone: string;
|
||||||
|
schedule: Schedule[];
|
||||||
|
};
|
@ -18,11 +18,13 @@ router.get('/', (req, res) => {
|
|||||||
<legend>Мастера</legend>
|
<legend>Мастера</legend>
|
||||||
${generateRadioInput('masters', 'success')}
|
${generateRadioInput('masters', 'success')}
|
||||||
${generateRadioInput('masters', 'error')}
|
${generateRadioInput('masters', 'error')}
|
||||||
|
${generateRadioInput('masters', 'empty')}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Заказы</legend>
|
<legend>Заказы</legend>
|
||||||
${generateRadioInput('orders', 'success')}
|
${generateRadioInput('orders', 'success')}
|
||||||
${generateRadioInput('orders', 'error')}
|
${generateRadioInput('orders', 'error')}
|
||||||
|
${generateRadioInput('orders', 'empty')}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Лендинг - Детали заказа</legend>
|
<legend>Лендинг - Детали заказа</legend>
|
||||||
@ -40,4 +42,4 @@ function generateRadioInput(name, type) {
|
|||||||
<input ${STUBS[name] === type ? 'checked' : ''} onclick="fetch('/api/admin/set/${name}/${type}')" type="radio" name="${name}">
|
<input ${STUBS[name] === type ? 'checked' : ''} onclick="fetch('/api/admin/set/${name}/${type}')" type="radio" name="${name}">
|
||||||
${type}
|
${type}
|
||||||
</label>`;
|
</label>`;
|
||||||
}
|
}
|
||||||
|
4
stubs/json/arm-masters/empty.json
Normal file
4
stubs/json/arm-masters/empty.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"body": []
|
||||||
|
}
|
4
stubs/json/arm-orders/empty.json
Normal file
4
stubs/json/arm-orders/empty.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"body": []
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user