начал делать создание приема

This commit is contained in:
Андрей Дувакин 2025-06-01 13:04:46 +05:00
parent b0e654a367
commit 88ee83047d
27 changed files with 803 additions and 134 deletions

View File

@ -20,6 +20,7 @@
"prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-quill": "^2.0.0",
"react-redux": "^9.2.0",
"react-router-dom": "^7.1.1",
"validator": "^13.12.0"
@ -1515,6 +1516,15 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/@types/quill": {
"version": "1.3.10",
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
"integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==",
"license": "MIT",
"dependencies": {
"parchment": "^1.1.2"
}
},
"node_modules/@types/react": {
"version": "18.3.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
@ -1906,7 +1916,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.0",
@ -1938,7 +1947,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@ -1984,6 +1992,15 @@
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"license": "MIT"
},
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"license": "MIT",
"engines": {
"node": ">=0.8"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -2146,6 +2163,26 @@
}
}
},
"node_modules/deep-equal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
"license": "MIT",
"dependencies": {
"is-arguments": "^1.1.1",
"is-date-object": "^1.0.5",
"is-regex": "^1.1.4",
"object-is": "^1.1.5",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.5.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -2157,7 +2194,6 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
@ -2175,7 +2211,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dev": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.0.1",
@ -2660,6 +2695,18 @@
"node": ">=0.10.0"
}
},
"node_modules/eventemitter3": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==",
"license": "MIT"
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -2667,6 +2714,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==",
"license": "Apache-2.0"
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@ -2832,7 +2885,6 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@ -2975,7 +3027,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0"
@ -3110,6 +3161,22 @@
"node": ">= 0.4"
}
},
"node_modules/is-arguments": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@ -3232,7 +3299,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
"integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
@ -3337,7 +3403,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
@ -3615,6 +3680,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -3732,11 +3803,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -3885,6 +3971,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==",
"license": "BSD-3-Clause"
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -4008,6 +4100,34 @@
"node": ">=6"
}
},
"node_modules/quill": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
"integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
"license": "BSD-3-Clause",
"dependencies": {
"clone": "^2.1.1",
"deep-equal": "^1.0.1",
"eventemitter3": "^2.0.3",
"extend": "^3.0.2",
"parchment": "^1.1.4",
"quill-delta": "^3.6.2"
}
},
"node_modules/quill-delta": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
"license": "MIT",
"dependencies": {
"deep-equal": "^1.0.1",
"extend": "^3.0.2",
"fast-diff": "1.1.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/rc-cascader": {
"version": "3.33.1",
"resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.33.1.tgz",
@ -4651,6 +4771,21 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/react-quill": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-quill/-/react-quill-2.0.0.tgz",
"integrity": "sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg==",
"license": "MIT",
"dependencies": {
"@types/quill": "^1.3.10",
"lodash": "^4.17.4",
"quill": "^1.3.7"
},
"peerDependencies": {
"react": "^16 || ^17 || ^18",
"react-dom": "^16 || ^17 || ^18"
}
},
"node_modules/react-redux": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
@ -4762,7 +4897,6 @@
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
"integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.8",
@ -4951,7 +5085,6 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dev": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.1.4",
@ -4969,7 +5102,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.1.4",

View File

@ -22,6 +22,7 @@
"prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-quill": "^2.0.0",
"react-redux": "^9.2.0",
"react-router-dom": "^7.1.1",
"validator": "^13.12.0"

View File

@ -0,0 +1,23 @@
import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/query/react";
import CONFIG from "../Core/сonfig.js";3
export const appointmentTypesApi = createApi({
reducerPath: 'appointmentTypesApi',
baseQuery: fetchBaseQuery({
baseUrl: CONFIG.BASE_URL,
prepareHeaders: (headers) => {
const token = localStorage.getItem('access_token');
if (token) headers.set('Authorization', `Bearer ${token}`);
return headers;
}
}),
tagsTypes: ['AppointmentTypes'],
endpoints: (builder) => ({
getAppointmentTypes: builder.query({
query: () => '/appointment_types/',
providesTags: ['AppointmentTypes'],
}),
}),
});
export const {useGetAppointmentTypesQuery} = appointmentTypesApi;

View File

@ -1,7 +1,6 @@
import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/query/react";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import CONFIG from "../Core/сonfig.js";
export const appointmentsApi = createApi({
reducerPath: 'appointmentsApi',
baseQuery: fetchBaseQuery({
@ -10,18 +9,36 @@ export const appointmentsApi = createApi({
const token = localStorage.getItem('access_token');
if (token) headers.set('Authorization', `Bearer ${token}`);
return headers;
}
},
}),
tagTypes: ['Appointment'],
endpoints: (builder) => ({
getAppointments: builder.query({
query: () => '/appointments/',
providesTags: ['Appointment'],
refetchOnMountOrArgChange: 5
refetchOnMountOrArgChange: 5,
}),
createAppointment: builder.mutation({
query: (data) => ({
url: '/appointments/',
method: 'POST',
body: data,
}),
invalidatesTags: ['Appointment'],
}),
updateAppointment: builder.mutation({
query: ({ id, data }) => ({
url: `/appointments/${id}/`,
method: 'PUT',
body: data,
}),
invalidatesTags: ['Appointment'],
}),
}),
});
export const {
useGetAppointmentsQuery,
useCreateAppointmentMutation,
useUpdateAppointmentMutation,
} = appointmentsApi;

View File

@ -1,7 +1,6 @@
import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/query/react";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import CONFIG from "../Core/сonfig.js";
export const scheduledAppointmentsApi = createApi({
reducerPath: 'scheduledAppointmentsApi',
baseQuery: fetchBaseQuery({
@ -10,16 +9,35 @@ export const scheduledAppointmentsApi = createApi({
const token = localStorage.getItem('access_token');
if (token) headers.set('Authorization', `Bearer ${token}`);
return headers;
}
},
}),
tagTypes: ['ScheduledAppointment'],
endpoints: (builder) => ({
getScheduledAppointments: builder.query({
query: () => `/scheduledAppointments`,
query: () => `/scheduled_appointments/`,
providesTags: ['ScheduledAppointment'],
}),
createScheduledAppointment: builder.mutation({
query: (data) => ({
url: '/scheduled_appointments/',
method: 'POST',
body: data,
}),
invalidatesTags: ['ScheduledAppointment'],
}),
updateScheduledAppointment: builder.mutation({
query: ({ id, data }) => ({
url: `/scheduled_appointments/${id}/`,
method: 'PUT',
body: data,
}),
invalidatesTags: ['ScheduledAppointment'],
}),
}),
});
export const {
useGetScheduledAppointmentsQuery,
useCreateScheduledAppointmentMutation,
useUpdateScheduledAppointmentMutation,
} = scheduledAppointmentsApi;

View File

@ -1,4 +1,4 @@
import {Card, Modal, Popconfirm, Tooltip} from "antd";
import {Card, Popconfirm, Tooltip} from "antd";
import PropTypes from "prop-types";
import {DeleteOutlined, EditOutlined, EyeOutlined} from "@ant-design/icons";
import {useState} from "react";

View File

@ -1,74 +1,130 @@
import {Button, Tabs, Typography} from "antd";
import {Button, FloatButton, Result, Tabs, Typography} from "antd";
import {Splitter} from "antd";
import {
CalendarOutlined, TableOutlined, MenuFoldOutlined, MenuUnfoldOutlined,
} from "@ant-design/icons";
import {CalendarOutlined, TableOutlined, MenuFoldOutlined, MenuUnfoldOutlined, PlusOutlined} from "@ant-design/icons";
import AppointmentsCalendarTab from "./Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx";
import AppointmentsTableTab from "./Components/AppointmentTableTab/AppointmentsTableTab.jsx";
import useAppointmentsUI from "./useAppointmentsUI.js";
import useAppointments from "./useAppointments.js";
import dayjs from 'dayjs';
import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx";
import AppointmentFormModal
from "./Components/AppointmentCalendarTab/Components/AppointmentFormModal/AppointmentFormModal.jsx";
const AppointmentsPage = () => {
const appointmentsPageUI = useAppointmentsUI();
const appointmentsData = useAppointments();
const appointmentsPageUI = useAppointmentsUI(appointmentsData.appointments, appointmentsData.scheduledAppointments);
const items = [{
key: "1",
label: "Календарь приемов",
children: <AppointmentsCalendarTab/>,
icon: <CalendarOutlined/>,
}, {
key: "2",
label: "Таблица приемов",
children: <AppointmentsTableTab/>,
icon: <TableOutlined/>,
},];
const items = [
{
key: "1",
label: "Календарь приемов",
children: <AppointmentsCalendarTab/>,
icon: <CalendarOutlined/>,
},
{
key: "2",
label: "Таблица приемов",
children: <AppointmentsTableTab/>,
icon: <TableOutlined/>,
},
];
if (appointmentsData.isError) return (
<Result
status="error"
title="Ошибка"
subTitle="Произошла ошибка в работе страницы"
/>
);
return (
<>
<Splitter
style={appointmentsPageUI.splitterStyle}
min={200}
max={400}
initial={appointmentsPageUI.siderWidth}
onChange={appointmentsPageUI.setSiderWidth}
>
<Splitter.Panel
style={appointmentsPageUI.splitterContentPanelStyle}
defaultSize="80%"
min="25%"
max="90%"
>
<Tabs defaultActiveKey="1" items={items}/>
</Splitter.Panel>
{appointmentsPageUI.showSplitterPanel && (
<Splitter.Panel
style={appointmentsPageUI.splitterSiderPanelStyle}
defaultSize="20%"
min="20%"
max="75%"
{appointmentsData.isLoading ? (
<LoadingIndicator/>
) : (
<>
<Splitter
style={appointmentsPageUI.splitterStyle}
min={200}
max={400}
initial={appointmentsPageUI.siderWidth}
onChange={appointmentsPageUI.setSiderWidth}
>
<Typography.Title level={3} style={appointmentsPageUI.siderTitleStyle}>
Предстоящие события
</Typography.Title>
<p>Здесь будут предстоящие приемы...</p>
</Splitter.Panel>
)}
</Splitter>
<div
style={appointmentsPageUI.siderButtonContainerStyle}
onMouseEnter={appointmentsPageUI.handleHoverSider}
onMouseLeave={appointmentsPageUI.handleLeaveSider}
>
<Button
type="primary"
onClick={appointmentsPageUI.handleToggleSider}
icon={appointmentsPageUI.collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>}
style={appointmentsPageUI.siderButtonStyle}
>
{appointmentsPageUI.siderButtonText}
</Button>
</div>
<Splitter.Panel
style={appointmentsPageUI.splitterContentPanelStyle}
defaultSize="80%"
min="25%"
max="90%"
>
<Tabs defaultActiveKey="1" items={items}/>
</Splitter.Panel>
{appointmentsPageUI.showSplitterPanel && (
<Splitter.Panel
style={appointmentsPageUI.splitterSiderPanelStyle}
defaultSize="20%"
min="20%"
max="75%"
>
<Typography.Title level={3} style={appointmentsPageUI.siderTitleStyle}>
Предстоящие события
</Typography.Title>
{appointmentsPageUI.upcomingEvents.length ? (
<ul>
{appointmentsPageUI.upcomingEvents.map(app => (
<li key={app.id}>
{dayjs(app.appointment_datetime || app.scheduled_datetime)
.tz('Europe/Moscow')
.format('DD.MM.YYYY HH:mm')} -
{app.appointment_datetime ? 'Прием' : 'Запланировано'}
</li>
))}
</ul>
) : (
<p>Нет предстоящих событий</p>
)}
</Splitter.Panel>
)}
</Splitter>
<div
style={appointmentsPageUI.siderButtonContainerStyle}
onMouseEnter={appointmentsPageUI.handleHoverSider}
onMouseLeave={appointmentsPageUI.handleLeaveSider}
>
<Button
type="primary"
onClick={appointmentsPageUI.handleToggleSider}
icon={appointmentsPageUI.collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>}
style={appointmentsPageUI.siderButtonStyle}
>
{appointmentsPageUI.siderButtonText}
</Button>
</div>
<FloatButton.Group
trigger={"hover"}
type="primary"
icon={<PlusOutlined/>}
tooltip={"Создать"}
>
<FloatButton
icon={<PlusOutlined/>}
onClick={appointmentsPageUI.openCreateAppointmentModal}
tooltip={"Прием"}
/>
<FloatButton
icon={<CalendarOutlined/>}
onClick={appointmentsPageUI.openCreateScheduledAppointmentModal}
tooltip={"Запланированный прием"}
/>
</FloatButton.Group>
<AppointmentFormModal
visible={appointmentsPageUI.modalVisible}
onCancel={appointmentsPageUI.handleCloseModal}
onSubmit={appointmentsData.handleSubmitModal}
/>
</>
)}
</>
);
};

View File

@ -1,25 +1,52 @@
import {Calendar} from "antd";
import {Calendar, Modal, Form, Input, DatePicker, Button} from "antd";
import 'dayjs/locale/ru';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import CalendarCell from "../../../../Widgets/CalendarCell.jsx";
import useAppointments from "../../useAppointments.js";
import useAppointmentCalendarUI from "./useAppointmentCalendarUI.js";
import {
closeModal,
openModal,
setSelectedAppointment,
setSelectedScheduledAppointment,
} from "../../../../../Redux/Slices/appointmentsSlice.js"
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.tz.setDefault('Asia/Almaty');
const AppointmentsCalendarTab = () => {
const appointmentsData = useAppointments();
const appointmentsCalendarUI = useAppointmentCalendarUI(appointmentsData.appointments, appointmentsData.scheduledAppointments);
const appointmentsCalendarUI = useAppointmentCalendarUI(
appointmentsData.appointments,
appointmentsData.scheduledAppointments
);
const dateCellRender = (value) => {
const appointmentsForDate = appointmentsCalendarUI.getAppointmentsByListAndDate(appointmentsData.appointments, value);
const scheduledForDate = appointmentsCalendarUI.getAppointmentsByListAndDate(appointmentsData.scheduledAppointments, value);
const appointmentsForDate = appointmentsCalendarUI.getAppointmentsByListAndDate(
appointmentsData.appointments,
value
);
const scheduledForDate = appointmentsCalendarUI.getAppointmentsByListAndDate(
appointmentsData.scheduledAppointments,
value,
true
);
return (
<CalendarCell
appointments={appointmentsForDate}
scheduledAppointments={scheduledForDate}
onCellClick={() => {
}}
onItemClick={() => {
onCellClick={() => appointmentsCalendarUI.onSelect(value)}
onItemClick={(appointment) => {
if (appointment.appointment_datetime) {
dispatch(setSelectedAppointment(appointment));
} else {
dispatch(setSelectedScheduledAppointment(appointment));
}
dispatch(openModal());
}}
/>
);

View File

@ -0,0 +1,120 @@
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import dayjs from "dayjs";
import {Button, DatePicker, Form, InputNumber, Modal, Result, Select} from "antd";
import useAppointmentFormModal from "./useAppointmentFormModal.js";
import useAppointmentFormModalUI from "./useAppointmentFormModalUI.js";
import LoadingIndicator from "../../../../../../Widgets/LoadingIndicator.jsx";
import {DefaultModalPropType} from "../../../../../../../Types/defaultModalPropType.js";
const AppointmentFormModal = ({visible, onCancel, onSubmit}) => {
const appointmentFormModalData = useAppointmentFormModal();
const appointmentFormModalUI = useAppointmentFormModalUI(visible, onCancel, onSubmit);
if (appointmentFormModalData.isError) {
return (
<Result
status="error"
title="Ошибка"
subTitle="Произошла ошибка в работе страницы"
/>
);
}
return (
<>
{appointmentFormModalData.isLoading ? (
<LoadingIndicator/>
) : (
<Modal
title={appointmentFormModalUI.selectedAppointment ? "Редактировать прием" : "Создать прием"}
open={appointmentFormModalUI.modalVisible}
onCancel={appointmentFormModalUI.onCancel}
footer={null}
>
<Form
form={appointmentFormModalUI.form}
onFinish={appointmentFormModalUI.onFinish}
initialValues={
appointmentFormModalUI.selectedAppointment
? {
patient_id: appointmentFormModalUI.selectedAppointment.patient_id,
type_id: appointmentFormModalUI.selectedAppointment.type_id,
appointmentTime: dayjs(appointmentFormModalUI.selectedAppointment.appointment_datetime).tz('Europe/Moscow'),
days_until_the_next_appointment: appointmentFormModalUI.selectedAppointment.days_until_the_next_appointment,
results: appointmentFormModalUI.selectedAppointment.results,
}
: {}
}
layout="vertical"
>
<Form.Item
name="patient_id"
label="Пациент"
rules={[{required: true, message: 'Выберите пациента'}]}
>
<Select
showSearch
optionFilterProp="children"
filterOption={(input, option) =>
option.children.toLowerCase().includes(input.toLowerCase())
}
placeholder="Выберите пациента"
>
{appointmentFormModalData.patients.map(patient => (
<Select.Option key={patient.id} value={patient.id}>
{patient.last_name} {patient.first_name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="type_id"
label="Тип приема"
rules={[{required: true, message: 'Выберите тип приема'}]}
>
<Select placeholder="Выберите тип приема">
{appointmentFormModalData.appointmentTypes.map(type => (
<Select.Option key={type.id} value={type.id}>
{type.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="appointment_datetime"
label="Время приема"
rules={[{required: true, message: 'Выберите время'}]}
>
<DatePicker defaultValue={dayjs().tz('Asia/Almaty')} showTime format="DD.MM.YYYY HH:mm"
style={{width: '100%'}}/>
</Form.Item>
<Form.Item
name="days_until_the_next_appointment"
label="Дней до следующего приема"
rules={[{type: 'number', min: 0, message: 'Введите неотрицательное число'}]}
>
<InputNumber min={0} style={{width: '100%'}}/>
</Form.Item>
<Form.Item
name="results"
label="Результаты приема"
>
<ReactQuill theme="snow"></ReactQuill>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
{appointmentFormModalUI.selectedAppointment ? 'Сохранить' : 'Создать'}
</Button>
</Form.Item>
</Form>
</Modal>
)}
</>
);
};
AppointmentFormModal.propTypes = DefaultModalPropType;
export default AppointmentFormModal;

View File

@ -0,0 +1,28 @@
import {useGetPatientsQuery} from "../../../../../../../Api/patientsApi.js";
import {useGetAppointmentTypesQuery} from "../../../../../../../Api/appointmentTypesApi.js";
const useAppointmentFormModal = () => {
const {
data: patients = [],
isLoading: isLoadingPatients,
isError: isErrorPatients
} = useGetPatientsQuery(undefined, {
pollingInterval: 20000,
});
const {
data: appointmentTypes = [],
isLoading: isLoadingAppointmentTypes,
isError: isErrorAppointmentTypes
} = useGetAppointmentTypesQuery(undefined, {
pollingInterval: 20000,
});
return {
patients,
appointmentTypes,
isLoading: isLoadingPatients || isLoadingAppointmentTypes,
isError: isErrorPatients || isErrorAppointmentTypes,
};
};
export default useAppointmentFormModal;

View File

@ -0,0 +1,55 @@
import {Form, message, notification} from "antd";
import { useDispatch, useSelector } from "react-redux";
import {closeModal} from "../../../../../../../Redux/Slices/appointmentsSlice.js";
import {useEffect} from "react";
import dayjs from "dayjs";
const useAppointmentFormModalUI = (visible, onCancel, onSubmit) => {
const { modalVisible, selectedAppointment } = useSelector(state => state.appointmentsUI);
const [form] = Form.useForm();
useEffect(() => {
if (visible) {
form.resetFields();
if (selectedAppointment) {
form.setFieldsValue({
...selectedAppointment,
appointment_datetime: selectedAppointment.appointment_datetime ? dayjs(selectedAppointment.appointment_datetime, "YYYY-MM-DD HH:mm") : null,
});
}
}
}, []);
const handleOk = async () => {
try {
const values = await form.validateFields();
if (values.birthday) {
values.appointment_datetime = values.appointment_datetime.format("YYYY-MM-DD HH:mm");
}
onSubmit(values);
form.resetFields();
} catch (error) {
console.log("Validation Failed:", error);
notification.error({
message: "Ошибка валидации",
description: "Проверьте правильность заполнения полей.",
placement: "topRight",
});
}
};
const handleCancel = () => {
form.resetFields();
onCancel();
};
return {
form,
modalVisible,
selectedAppointment,
handleOk,
handleCancel,
};
};
export default useAppointmentFormModalUI;

View File

@ -0,0 +1,13 @@
import {useCreateAppointmentMutation, useUpdateAppointmentMutation} from "../../../../../Api/appointmentsApi.js";
import {
useCreateScheduledAppointmentMutation,
useUpdateScheduledAppointmentMutation
} from "../../../../../Api/scheduledAppointmentsApi.js";
const useAppointmentCalendar = () => {
const [createAppointment] = useCreateAppointmentMutation();
const [updateAppointment] = useUpdateAppointmentMutation();
const [createScheduledAppointment] = useCreateScheduledAppointmentMutation();
const [updateScheduledAppointment] = useUpdateScheduledAppointmentMutation();
};

View File

@ -1,11 +1,24 @@
// useAppointmentCalendarUI.js
import {useDispatch, useSelector} from "react-redux";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import {Form, Grid, notification} from "antd";
import {
openModal,
closeModal,
openModal, setSelectedAppointment,
setSelectedAppointments,
setSelectedDate
setSelectedDate, setSelectedScheduledAppointment,
} from "../../../../../Redux/Slices/appointmentsSlice.js";
import {Grid} from "antd";
import {useCreateAppointmentMutation, useUpdateAppointmentMutation} from "../../../../../Api/appointmentsApi.js";
import {
useCreateScheduledAppointmentMutation,
useUpdateScheduledAppointmentMutation
} from "../../../../../Api/scheduledAppointmentsApi.js";
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.tz.setDefault('Europe/Moscow');
const {useBreakpoint} = Grid;
@ -15,9 +28,9 @@ const useAppointmentCalendarUI = (appointments, scheduledAppointments) => {
modalVisible,
selectedAppointments,
selectedAppointment,
selectedScheduledAppointment,
} = useSelector(state => state.appointmentsUI);
const selectedDate = dayjs(useSelector(state => state.appointmentsUI.selectedDate));
const selectedDate = dayjs.tz(useSelector(state => state.appointmentsUI.selectedDate), 'Europe/Moscow');
const screens = useBreakpoint();
const fullScreenCalendar = !screens.xs;
@ -25,29 +38,114 @@ const useAppointmentCalendarUI = (appointments, scheduledAppointments) => {
const calendarContainerStyle = {padding: 20};
const onSelect = (date) => {
const selectedDateStr = date.format('YYYY-MM-DD');
const selectedDateStr = date.tz('Europe/Moscow').format('YYYY-MM-DD');
dispatch(setSelectedDate(selectedDateStr));
console.log(appointments)
const appointmentsForDate = appointments.filter(app =>
dayjs(app.appointment_datetime).format('YYYY-MM-DD') === selectedDateStr
dayjs(app.appointment_datetime).tz('Europe/Moscow').format('YYYY-MM-DD') === selectedDateStr
);
console.log(appointmentsForDate)
const scheduledForDate = scheduledAppointments.filter(app =>
dayjs(app.scheduled_datetime).format('YYYY-MM-DD') === selectedDateStr
dayjs(app.scheduled_datetime).tz('Europe/Moscow').format('YYYY-MM-DD') === selectedDateStr
);
dispatch(setSelectedAppointments([...appointmentsForDate, ...scheduledForDate]));
dispatch(openModal());
};
const getAppointmentsByListAndDate = (list, value) => {
const date = value.format('YYYY-MM-DD');
const onFinish = async (values) => {
try {
const appointmentTime = values.appointmentTime;
const isScheduled = !(selectedAppointment?.appointment_datetime);
const conflictingAppointments = isScheduled
? scheduledAppointments
: appointments;
const hasConflict = conflictingAppointments.some(app =>
dayjs(app.appointment_datetime || app.scheduled_datetime)
.tz('Europe/Moscow')
.isSame(appointmentTime, 'minute')
);
if (hasConflict) {
notification.error({
message: "Выбранное время уже занято",
description: "Выбранное время уже занято",
placement: "topRight",
});
return;
}
const data = {
patient: {
last_name: values.patientName.split(' ')[0] || '',
first_name: values.patientName.split(' ')[1] || '',
},
...(isScheduled
? { scheduled_datetime: appointmentTime.toISOString() }
: { appointment_datetime: appointmentTime.toISOString() }),
reason: values.reason,
};
if (selectedAppointment || selectedScheduledAppointment) {
if (selectedAppointment?.appointment_datetime) {
await updateAppointment({ id: selectedAppointment.id, data }).unwrap();
notification.success({
message: "Прием успешно обновлен",
description: "Прием успешно обновлен",
placement: "topRight",
});
} else {
await updateScheduledAppointment({ id: selectedScheduledAppointment.id, data }).unwrap();
notification.success({
message: "Запланированный прием успешно обновлен",
description: "Запланированный прием успешно обновлен",
placement: "topRight",
});
}
} else {
if (isScheduled) {
await createScheduledAppointment(data).unwrap();
notification.success({
message: "Запланированный прием успешно создан",
description: "Запланированный прием успешно создан",
placement: "topRight",
});
} else {
await createAppointment(data).unwrap();
notification.success({
message: "Прием успешно создан",
description: "Прием успешно создан",
placement: "topRight",
});
}
}
dispatch(closeModal());
form.resetFields();
} catch (error) {
notification.error({
message: "Ошибка при сохранении приема",
description: error.data?.message || "Не удалось сохранить прием",
placement: "topRight",
});
}
};
const handleCreateAppointment = () => {
dispatch(setSelectedAppointment(null));
dispatch(setSelectedScheduledAppointment(null));
form.resetFields();
dispatch(openModal());
};
const getAppointmentsByListAndDate = (list, value, isScheduled = false) => {
const date = value.tz('Europe/Moscow').format('YYYY-MM-DD');
return list.filter(app =>
dayjs(app.appointment_datetime).format('YYYY-MM-DD') === date
dayjs(isScheduled ? app.scheduled_datetime : app.appointment_datetime)
.tz('Europe/Moscow')
.format('YYYY-MM-DD') === date
);
}
};
return {
selectedDate,

View File

@ -1,28 +1,74 @@
import {useGetAppointmentsQuery} from "../../../Api/appointmentsApi.js";
import {useCreateAppointmentMutation, useGetAppointmentsQuery} from "../../../Api/appointmentsApi.js";
import {useGetScheduledAppointmentsQuery} from "../../../Api/scheduledAppointmentsApi.js";
import {useGetPatientsQuery} from "../../../Api/patientsApi.js";
import {notification} from "antd";
import {closeModal} from "../../../Redux/Slices/appointmentsSlice.js";
import {useDispatch} from "react-redux";
const useAppointments = () => {
const dispatch = useDispatch();
const [createAppointment] = useCreateAppointmentMutation();
const {
data: appointments = [],
isLoadingAppointments,
isErrorAppointments,
isLoading: isLoadingAppointments,
isError: isErrorAppointments,
} = useGetAppointmentsQuery(undefined, {
pollingInterval: 20000,
});
const {
data: scheduledAppointments = [],
isLoadingScheduledAppointments,
isErrorScheduledAppointments,
} = useGetAppointmentsQuery(undefined, {
isLoading: isLoadingScheduledAppointments,
isError: isErrorScheduledAppointments,
} = useGetScheduledAppointmentsQuery(undefined, {
pollingInterval: 20000,
});
const {
data: patients = [],
isLoading: isLoadingPatients,
isError: isErrorPatients
} = useGetPatientsQuery(undefined, {
pollingInterval: 20000
});
const handleSubmitModal = async (values) => {
try {
const appointmentTime = values.appointmentTime;
const data = {
patient_id: values.patient_id,
type_id: values.type_id,
appointment_datetime: appointmentTime.toISOString(),
days_until_the_next_appointment: values.days_until_the_next_appointment,
results: values.results,
doctor_id: localStorage.getItem('doctor_id'),
};
await createAppointment(data).unwrap();
notification.success({
message: 'Прием создан',
description: 'Прием успешно создан',
placement: 'topRight',
});
dispatch(closeModal());
} catch (error) {
notification.error({
message: 'Ошибка при создании приема',
description: error.data?.message || 'Не удалось создать прием',
placement: 'topRight',
});
}
};
return {
patients,
appointments,
scheduledAppointments,
isLoading: isLoadingAppointments || isLoadingScheduledAppointments,
isError: isErrorAppointments || isErrorScheduledAppointments,
handleSubmitModal,
isLoading: isLoadingAppointments || isLoadingScheduledAppointments || isLoadingPatients,
isError: isErrorAppointments || isErrorScheduledAppointments || isErrorPatients,
};
};

View File

@ -1,16 +1,18 @@
import {useDispatch, useSelector} from "react-redux";
import {Grid} from "antd";
import {setHovered, toggleSider} from "../../../Redux/Slices/appointmentsSlice.js";
import {openModal, setHovered, toggleSider} from "../../../Redux/Slices/appointmentsSlice.js";
import {useEffect, useMemo} from "react";
import dayjs from "dayjs";
const {useBreakpoint} = Grid;
const useAppointmentsUI = () => {
const useAppointmentsUI = (appointments, scheduledAppointments) => {
const dispatch = useDispatch();
const {
collapsed,
siderWidth,
hovered,
modalVisible,
} = useSelector(state => state.appointmentsUI);
const screens = useBreakpoint();
@ -36,7 +38,7 @@ const useAppointmentsUI = () => {
padding: hovered ? "0 20px" : "0",
overflow: "hidden",
textAlign: "left",
transition: "width 0.3s ease, padding 0.3s ease",
transition: "width 0.8s ease, padding 0.8s ease",
borderRadius: "4px 0 0 4px",
};
@ -44,9 +46,18 @@ const useAppointmentsUI = () => {
const handleHoverSider = () => dispatch(setHovered(true));
const handleLeaveSider = () => dispatch(setHovered(false));
const openCreateAppointmentModal = () => dispatch(openModal());
const handleCloseModal = () => dispatch(openModal(false));
const handleModalSubmit = () => dispatch(openModal(false));
const siderButtonText = useMemo(() => hovered ? (collapsed ? "Показать предстоящие события" : "Скрыть предстоящие события") : "", [collapsed, hovered]);
const showSplitterPanel = useMemo(() => !collapsed && !screens.xs, [collapsed, screens]);
const upcomingEvents = [...appointments, ...scheduledAppointments]
.filter(app => dayjs(app.appointment_datetime || app.scheduled_datetime).isAfter(dayjs()))
.sort((a, b) => dayjs(a.appointment_datetime || a.scheduled_datetime) - dayjs(b.appointment_datetime || b.scheduled_datetime))
.slice(0, 5);
return {
collapsed,
siderWidth,
@ -59,9 +70,14 @@ const useAppointmentsUI = () => {
siderTitleStyle,
siderButtonContainerStyle,
siderButtonStyle,
upcomingEvents,
modalVisible,
handleToggleSider,
handleHoverSider,
handleLeaveSider,
openCreateAppointmentModal,
handleCloseModal,
handleModalSubmit,
};
};

View File

@ -189,8 +189,6 @@ const LensIssueFormModal = ({visible, onCancel, onSubmit}) => {
title: 'Подтверждение', content: ConfirmStep,
}];
console.log(steps[lensIssueFormModalUI.currentStep].title)
return (
<Modal
title="Выдача линзы пациенту"

View File

@ -10,8 +10,6 @@ const useLensIssueForm = () => {
pollingInterval: 10000,
});
console.log(lenses)
return {
patients,
lenses,

View File

@ -6,9 +6,6 @@ import {closeModal} from "../../../Redux/Slices/lensIssuesSlice.js";
const useIssues = () => {
const dispatch = useDispatch();
const {
selectedIssue,
} = useSelector(state => state.lensIssuesUI);
const {data: issues = [], isLoading, isError, error} = useGetLensIssuesQuery(undefined, {
pollingInterval: 20000,
@ -39,7 +36,6 @@ const useIssues = () => {
isLoading,
isError,
error,
selectedIssue,
handleSubmitFormModal,
};
};

View File

@ -28,6 +28,7 @@ const useIssuesUI = (issues) => {
endFilterDate,
} = useSelector(state => state.lensIssuesUI);
useEffect(() => {
document.title = "Выдача линз";
const cachedViewMode = getCachedInfo("viewModeIssues");

View File

@ -8,8 +8,8 @@ import usePatientFormUI from "./usePatientFormUI.js";
const {TextArea} = Input;
const PatientFormModal = ({visible, onCancel, onSubmit, patient}) => {
const patientFormModalUI = usePatientFormUI(visible, onCancel, onSubmit, patient);
const PatientFormModal = ({visible, onCancel, onSubmit}) => {
const patientFormModalUI = usePatientFormUI(visible, onCancel, onSubmit);
return (
<Modal

View File

@ -2,28 +2,31 @@ import {Form, notification} from "antd";
import {useEffect} from "react";
import dayjs from "dayjs";
import validator from "validator";
import {useSelector} from "react-redux";
const usePatientFormUI = (visible, onCancel, onSubmit, patient) => {
const usePatientFormUI = (visible, onCancel, onSubmit) => {
const {selectedPatient} = useSelector(state => state.patientsUI);
const [form] = Form.useForm();
useEffect(() => {
if (visible) {
form.resetFields();
if (patient) {
if (selectedPatient) {
form.setFieldsValue({
...patient,
birthday: patient.birthday ? dayjs(patient.birthday, "YYYY-MM-DD") : null,
...selectedPatient,
birthday: selectedPatient.birthday ? dayjs(selectedPatient.birthday, "YYYY-MM-DD") : null,
});
}
}
}, [visible, patient, form]);
}, [visible, selectedPatient, form]);
const modalStyle = {
marginTop: 20,
marginBottom: 50,
};
const modalTitle = patient ? "Редактировать пациента" : "Добавить пациента";
const modalTitle = selectedPatient ? "Редактировать пациента" : "Добавить пациента";
const emailValidator = (_, value) => {
if (value && !validator.isEmail(value)) {

View File

@ -190,7 +190,6 @@ const PatientsPage = () => {
visible={patientsUI.isModalVisible}
onCancel={patientsUI.handleCloseModal}
onSubmit={patientsData.handleModalSubmit}
patient={patientsUI.selectedPatient}
/>
</div>
);

View File

@ -18,7 +18,6 @@ const usePatientsUI = (patients) => {
searchText,
sortOrder,
viewMode,
selectedPatient,
isModalVisible,
currentPage,
pageSize,
@ -87,7 +86,6 @@ const usePatientsUI = (patients) => {
searchText,
sortOrder,
viewMode,
selectedPatient,
isModalVisible,
currentPage,
pageSize,

View File

@ -53,7 +53,7 @@ const CalendarCell = ({appointments, scheduledAppointments, onCellClick, onItemC
>
<Badge
status="success"
text={dayjs(app.scheduled_datetime).format('HH:mm') + ` ${app.patient.last_name} ${app.patient.first_name} `}
text={dayjs(app.appointment_datetime).format('HH:mm') + ` ${app.patient.last_name} ${app.patient.first_name} `}
/>
</Tag>
</Tooltip>

View File

@ -10,6 +10,8 @@ const initialState = {
modalVisible: false,
selectedAppointments: [],
selectedAppointment: null,
scheduledAppointments: [],
selectedScheduledAppointment: null,
};
const appointmentsSlice = createSlice({
@ -40,6 +42,12 @@ const appointmentsSlice = createSlice({
setSelectedAppointment: (state, action) => {
state.selectedAppointment = action.payload;
},
setScheduledAppointments: (state, action) => {
state.scheduledAppointments = action.payload;
},
setSelectedScheduledAppointment: (state, action) => {
state.selectedScheduledAppointment = action.payload;
},
}
});
@ -52,6 +60,8 @@ export const {
closeModal,
setSelectedAppointments,
setSelectedAppointment,
setSelectedScheduledAppointment,
setScheduledAppointments,
} = appointmentsSlice.actions;
export default appointmentsSlice.reducer;

View File

@ -11,6 +11,8 @@ import lensIssuesReducer from "./Slices/lensIssuesSlice.js";
import {lensTypesApi} from "../Api/lensTypesApi.js";
import {appointmentsApi} from "../Api/appointmentsApi.js";
import appointmentsReducer from "./Slices/appointmentsSlice.js";
import {scheduledAppointmentsApi} from "../Api/scheduledAppointmentsApi.js";
import {appointmentTypesApi} from "../Api/appointmentTypesApi.js";
export const store = configureStore({
reducer: {
@ -32,6 +34,10 @@ export const store = configureStore({
[appointmentsApi.reducerPath]: appointmentsApi.reducer,
appointmentsUI: appointmentsReducer,
[scheduledAppointmentsApi.reducerPath]: scheduledAppointmentsApi.reducer,
[appointmentTypesApi.reducerPath]: appointmentTypesApi.reducer,
},
middleware: (getDefaultMiddleware) => (
getDefaultMiddleware().concat(
@ -42,6 +48,8 @@ export const store = configureStore({
lensTypesApi.middleware,
lensIssuesApi.middleware,
appointmentsApi.middleware,
scheduledAppointmentsApi.middleware,
appointmentTypesApi.middleware,
)
),
});

View File

@ -0,0 +1,8 @@
import PropTypes from "prop-types";
export const DefaultModalPropType = PropTypes.shape({
visible: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
})