feat: add RTK for master
All checks were successful
it-academy/dry-wash-pl/pipeline/pr-main This commit looks good
it-academy/dry-wash-pl/pipeline/head This commit looks good

This commit is contained in:
2025-01-26 12:17:08 +03:00
parent adce5392a1
commit d15bd6f7d2
14 changed files with 224 additions and 69 deletions

View 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
View 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;

View File

@@ -1,19 +1,23 @@
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import { ChakraProvider, theme as chakraTheme } from '@chakra-ui/react';
import { Provider } from 'react-redux';
import Routers from './routes';
import ErrorBoundary from './components/ErrorBoundary';
import { store } from './__data__/store';
const App = () => {
return (
<ChakraProvider theme={chakraTheme}>
<ErrorBoundary>
<BrowserRouter>
<Routers />
</BrowserRouter>
</ErrorBoundary>
</ChakraProvider>
<Provider store={store}>
<ChakraProvider theme={chakraTheme}>
<ErrorBoundary>
<BrowserRouter>
<Routers />
</BrowserRouter>
</ErrorBoundary>
</ChakraProvider>
</Provider>
);
};

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import {
Button,
FormControl,
@@ -18,21 +18,29 @@ import {
import { useTranslation } from 'react-i18next';
import { PhoneIcon } from '@chakra-ui/icons';
import { armService } from '../../api/arm';
import { api } from '../../__data__/service/api';
const MasterDrawer = ({ isOpen, onClose }) => {
const { addMaster } = armService();
const [addMaster, { error, isSuccess }] = api.useAddMasterMutation();
const toast = useToast();
const [newMaster, setNewMaster] = useState({ name: '', phone: '' });
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;
}
try {
await addMaster(newMaster);
addMaster(trimMaster);
};
useEffect(() => {
if (isSuccess) {
toast({
title: 'Мастер создан.',
description: `Мастер "${newMaster.name}" успешно добавлен.`,
@@ -42,7 +50,11 @@ const MasterDrawer = ({ isOpen, onClose }) => {
position: 'top-right',
});
onClose();
} catch (error) {
}
}, [isSuccess]);
useEffect(() => {
if (error) {
toast({
title: 'Ошибка при создании мастера.',
description: 'Не удалось добавить мастера. Попробуйте еще раз.',
@@ -53,7 +65,7 @@ const MasterDrawer = ({ isOpen, onClose }) => {
});
console.error(error);
}
};
}, [error]);
const { t } = useTranslation('~', {
keyPrefix: 'dry-wash.arm.master.drawer',

View File

@@ -7,19 +7,6 @@ import { getTimeSlot } from '../../lib';
import EditableWrapper from '../Editable/Editable';
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 { updateMaster } = armService();
const { t } = useTranslation('~', {

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import {
Box,
Heading,
@@ -19,8 +19,7 @@ import { useTranslation } from 'react-i18next';
import MasterItem from '../MasterItem';
import MasterDrawer from '../MasterDrawer';
import { armService } from '../../api/arm';
import { MasterProps } from '../MasterItem/MasterItem';
import { api } from '../../__data__/service/api';
const TABLE_HEADERS = [
'name' as const,
@@ -36,35 +35,23 @@ const Masters = () => {
keyPrefix: 'dry-wash.arm.master',
});
const [masters, setMasters] = useState<MasterProps[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { fetchMasters } = armService();
const {
data: masters,
error,
isLoading,
isSuccess,
} = api.useGetMastersQuery();
useEffect(() => {
const loadMasters = async () => {
setLoading(true);
try {
const data = await fetchMasters();
setMasters(data.body);
} catch (err) {
setError(err.message);
toast({
title: t('error.title'),
status: 'error',
duration: 5000,
isClosable: true,
position: 'bottom-right',
});
} finally {
setLoading(false);
}
};
loadMasters();
}, [toast, t]);
if (error) {
toast({
title: t('error.title'),
status: 'error',
isClosable: true,
position: 'bottom-right',
});
}
}, [error]);
return (
<Box p='8'>
@@ -83,22 +70,21 @@ const Masters = () => {
</Tr>
</Thead>
<Tbody>
{loading && (
{isLoading && (
<Tr>
<Td colSpan={TABLE_HEADERS.length} textAlign='center' py='8'>
<Spinner size='lg' />
</Td>
</Tr>
)}
{!loading && masters.length === 0 && !error && (
{isSuccess && masters.length === 0 && (
<Tr>
<Td colSpan={TABLE_HEADERS.length}>
<Text>{t('table.empty')}</Text>
</Td>
</Tr>
)}
{!loading &&
!error &&
{isSuccess &&
masters.map((master, index) => (
<MasterItem key={index} {...master} />
))}

View File

@@ -3,8 +3,8 @@ import { Td, Tr, Link, Select } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs';
import { MasterProps } from '../MasterItem/MasterItem';
import { getTimeSlot } from '../../lib';
import { Master } from '../../models/api/master';
import { armService } from '../../api/arm';
const statuses = [
@@ -26,9 +26,9 @@ export type OrderProps = {
status?: GetArrItemType<typeof statuses>;
phone?: string;
location?: string;
master: MasterProps;
master: Master;
notes: '';
allMasters: MasterProps[];
allMasters: Master[];
id: string;
};

View File

@@ -19,7 +19,7 @@ import OrderItem from '../OrderItem';
import { OrderProps } from '../OrderItem/OrderItem';
import { armService } from '../../api/arm';
import DateNavigator from '../DateNavigator';
import { MasterProps } from '../MasterItem/MasterItem';
import { Master } from '../../models/api/master';
const TABLE_HEADERS = [
'carNumber' as const,
@@ -41,7 +41,7 @@ const Orders = () => {
const toast = useToast();
const [orders, setOrders] = useState<OrderProps[]>([]);
const [allMasters, setAllMasters] = useState<MasterProps[]>([]);
const [allMasters, setAllMasters] = useState<Master[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [currentDate, setCurrentDate] = useState(new Date());

12
src/models/api/master.ts Normal file
View 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[];
};