From 88ee83047d07038d116732d36927885f19e529f0 Mon Sep 17 00:00:00 2001 From: andrei Date: Sun, 1 Jun 2025 13:04:46 +0500 Subject: [PATCH] =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=20=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B0=D1=82=D1=8C=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D1=80=D0=B8=D0=B5=D0=BC=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web-app/package-lock.json | 156 ++++++++++++++-- web-app/package.json | 1 + web-app/src/Api/appointmentTypesApi.js | 23 +++ web-app/src/Api/appointmentsApi.js | 25 ++- web-app/src/Api/scheduledAppointmentsApi.js | 26 ++- .../Components/Dummies/PatientListCard.jsx | 2 +- .../AppointmentsPage/AppointmentsPage.jsx | 176 ++++++++++++------ .../AppointmentsCalendarTab.jsx | 41 +++- .../AppointmentFormModal.jsx | 120 ++++++++++++ .../useAppointmentFormModal.js | 28 +++ .../useAppointmentFormModalUI.js | 55 ++++++ .../useAppointmentCalendar.js | 13 ++ .../useAppointmentCalendarUI.js | 126 +++++++++++-- .../Pages/AppointmentsPage/useAppointments.js | 64 ++++++- .../AppointmentsPage/useAppointmentsUI.js | 22 ++- .../LensIssueFormModal/LensIssueFormModal.jsx | 2 - .../LensIssueFormModal/useLensIssueForm.js | 2 - .../Components/Pages/IssuesPage/useIssues.js | 4 - .../Pages/IssuesPage/useIssuesUI.js | 1 + .../PatientFormModal/PatientFormModal.jsx | 4 +- .../PatientFormModal/usePatientFormUI.js | 15 +- .../Pages/PatientsPage/PatientsPage.jsx | 1 - .../Pages/PatientsPage/usePatientsUI.js | 2 - .../src/Components/Widgets/CalendarCell.jsx | 2 +- web-app/src/Redux/Slices/appointmentsSlice.js | 10 + web-app/src/Redux/store.js | 8 + web-app/src/Types/defaultModalPropType.js | 8 + 27 files changed, 803 insertions(+), 134 deletions(-) create mode 100644 web-app/src/Api/appointmentTypesApi.js create mode 100644 web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/AppointmentFormModal.jsx create mode 100644 web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/useAppointmentFormModal.js create mode 100644 web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/useAppointmentFormModalUI.js create mode 100644 web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/useAppointmentCalendar.js create mode 100644 web-app/src/Types/defaultModalPropType.js diff --git a/web-app/package-lock.json b/web-app/package-lock.json index c0bec04..cc7c830 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -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", diff --git a/web-app/package.json b/web-app/package.json index 91901a9..bece5d0 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -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" diff --git a/web-app/src/Api/appointmentTypesApi.js b/web-app/src/Api/appointmentTypesApi.js new file mode 100644 index 0000000..76fc18e --- /dev/null +++ b/web-app/src/Api/appointmentTypesApi.js @@ -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; \ No newline at end of file diff --git a/web-app/src/Api/appointmentsApi.js b/web-app/src/Api/appointmentsApi.js index edce0e7..6a1c6ce 100644 --- a/web-app/src/Api/appointmentsApi.js +++ b/web-app/src/Api/appointmentsApi.js @@ -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; \ No newline at end of file diff --git a/web-app/src/Api/scheduledAppointmentsApi.js b/web-app/src/Api/scheduledAppointmentsApi.js index ea301f7..7adfe64 100644 --- a/web-app/src/Api/scheduledAppointmentsApi.js +++ b/web-app/src/Api/scheduledAppointmentsApi.js @@ -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; \ No newline at end of file diff --git a/web-app/src/Components/Dummies/PatientListCard.jsx b/web-app/src/Components/Dummies/PatientListCard.jsx index 3d283ee..b6ea159 100644 --- a/web-app/src/Components/Dummies/PatientListCard.jsx +++ b/web-app/src/Components/Dummies/PatientListCard.jsx @@ -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"; diff --git a/web-app/src/Components/Pages/AppointmentsPage/AppointmentsPage.jsx b/web-app/src/Components/Pages/AppointmentsPage/AppointmentsPage.jsx index e9b922f..4c7541e 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/AppointmentsPage.jsx +++ b/web-app/src/Components/Pages/AppointmentsPage/AppointmentsPage.jsx @@ -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: , - icon: , - }, { - key: "2", - label: "Таблица приемов", - children: , - icon: , - },]; + const items = [ + { + key: "1", + label: "Календарь приемов", + children: , + icon: , + }, + { + key: "2", + label: "Таблица приемов", + children: , + icon: , + }, + ]; + + if (appointmentsData.isError) return ( + + ); return ( <> - - - - - - {appointmentsPageUI.showSplitterPanel && ( - + ) : ( + <> + - - Предстоящие события - -

Здесь будут предстоящие приемы...

-
- )} -
-
- -
+ + + + + {appointmentsPageUI.showSplitterPanel && ( + + + Предстоящие события + + {appointmentsPageUI.upcomingEvents.length ? ( +
    + {appointmentsPageUI.upcomingEvents.map(app => ( +
  • + {dayjs(app.appointment_datetime || app.scheduled_datetime) + .tz('Europe/Moscow') + .format('DD.MM.YYYY HH:mm')} - + {app.appointment_datetime ? 'Прием' : 'Запланировано'} +
  • + ))} +
+ ) : ( +

Нет предстоящих событий

+ )} +
+ )} + +
+ +
+ } + tooltip={"Создать"} + > + } + onClick={appointmentsPageUI.openCreateAppointmentModal} + tooltip={"Прием"} + /> + } + onClick={appointmentsPageUI.openCreateScheduledAppointmentModal} + tooltip={"Запланированный прием"} + /> + + + + + )} ); }; diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx index bf40a19..aab6d78 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx @@ -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 ( { - }} - onItemClick={() => { + onCellClick={() => appointmentsCalendarUI.onSelect(value)} + onItemClick={(appointment) => { + if (appointment.appointment_datetime) { + dispatch(setSelectedAppointment(appointment)); + } else { + dispatch(setSelectedScheduledAppointment(appointment)); + } + dispatch(openModal()); }} /> ); diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/AppointmentFormModal.jsx b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/AppointmentFormModal.jsx new file mode 100644 index 0000000..da6bc83 --- /dev/null +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/AppointmentFormModal.jsx @@ -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 ( + + ); + } + + return ( + <> + {appointmentFormModalData.isLoading ? ( + + ) : ( + +
+ + + + + + + + + + + + + + + + + + +
+
+ )} + + ); +}; + +AppointmentFormModal.propTypes = DefaultModalPropType; + +export default AppointmentFormModal; \ No newline at end of file diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/useAppointmentFormModal.js b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/useAppointmentFormModal.js new file mode 100644 index 0000000..4d28336 --- /dev/null +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/useAppointmentFormModal.js @@ -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; \ No newline at end of file diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/useAppointmentFormModalUI.js b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/useAppointmentFormModalUI.js new file mode 100644 index 0000000..e708def --- /dev/null +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/useAppointmentFormModalUI.js @@ -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; \ No newline at end of file diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/useAppointmentCalendar.js b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/useAppointmentCalendar.js new file mode 100644 index 0000000..b840c95 --- /dev/null +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/useAppointmentCalendar.js @@ -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(); +}; \ No newline at end of file diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/useAppointmentCalendarUI.js b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/useAppointmentCalendarUI.js index f7edd9c..0c8c1b3 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/useAppointmentCalendarUI.js +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/useAppointmentCalendarUI.js @@ -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, diff --git a/web-app/src/Components/Pages/AppointmentsPage/useAppointments.js b/web-app/src/Components/Pages/AppointmentsPage/useAppointments.js index a0723a5..b4fe165 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/useAppointments.js +++ b/web-app/src/Components/Pages/AppointmentsPage/useAppointments.js @@ -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, }; }; diff --git a/web-app/src/Components/Pages/AppointmentsPage/useAppointmentsUI.js b/web-app/src/Components/Pages/AppointmentsPage/useAppointmentsUI.js index 662b85c..5eeb5cb 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/useAppointmentsUI.js +++ b/web-app/src/Components/Pages/AppointmentsPage/useAppointmentsUI.js @@ -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, }; }; diff --git a/web-app/src/Components/Pages/IssuesPage/Components/LensIssueFormModal/LensIssueFormModal.jsx b/web-app/src/Components/Pages/IssuesPage/Components/LensIssueFormModal/LensIssueFormModal.jsx index 702ca4e..5f64c45 100644 --- a/web-app/src/Components/Pages/IssuesPage/Components/LensIssueFormModal/LensIssueFormModal.jsx +++ b/web-app/src/Components/Pages/IssuesPage/Components/LensIssueFormModal/LensIssueFormModal.jsx @@ -189,8 +189,6 @@ const LensIssueFormModal = ({visible, onCancel, onSubmit}) => { title: 'Подтверждение', content: ConfirmStep, }]; - console.log(steps[lensIssueFormModalUI.currentStep].title) - return ( { pollingInterval: 10000, }); - console.log(lenses) - return { patients, lenses, diff --git a/web-app/src/Components/Pages/IssuesPage/useIssues.js b/web-app/src/Components/Pages/IssuesPage/useIssues.js index 9ab6899..d30a946 100644 --- a/web-app/src/Components/Pages/IssuesPage/useIssues.js +++ b/web-app/src/Components/Pages/IssuesPage/useIssues.js @@ -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, }; }; diff --git a/web-app/src/Components/Pages/IssuesPage/useIssuesUI.js b/web-app/src/Components/Pages/IssuesPage/useIssuesUI.js index 266f383..2999524 100644 --- a/web-app/src/Components/Pages/IssuesPage/useIssuesUI.js +++ b/web-app/src/Components/Pages/IssuesPage/useIssuesUI.js @@ -28,6 +28,7 @@ const useIssuesUI = (issues) => { endFilterDate, } = useSelector(state => state.lensIssuesUI); + useEffect(() => { document.title = "Выдача линз"; const cachedViewMode = getCachedInfo("viewModeIssues"); diff --git a/web-app/src/Components/Pages/PatientsPage/Components/PatientFormModal/PatientFormModal.jsx b/web-app/src/Components/Pages/PatientsPage/Components/PatientFormModal/PatientFormModal.jsx index 634d9c1..37d4f23 100644 --- a/web-app/src/Components/Pages/PatientsPage/Components/PatientFormModal/PatientFormModal.jsx +++ b/web-app/src/Components/Pages/PatientsPage/Components/PatientFormModal/PatientFormModal.jsx @@ -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 ( { +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)) { diff --git a/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx b/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx index 5236459..f26922f 100644 --- a/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx +++ b/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx @@ -190,7 +190,6 @@ const PatientsPage = () => { visible={patientsUI.isModalVisible} onCancel={patientsUI.handleCloseModal} onSubmit={patientsData.handleModalSubmit} - patient={patientsUI.selectedPatient} /> ); diff --git a/web-app/src/Components/Pages/PatientsPage/usePatientsUI.js b/web-app/src/Components/Pages/PatientsPage/usePatientsUI.js index bcd7c80..29c3154 100644 --- a/web-app/src/Components/Pages/PatientsPage/usePatientsUI.js +++ b/web-app/src/Components/Pages/PatientsPage/usePatientsUI.js @@ -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, diff --git a/web-app/src/Components/Widgets/CalendarCell.jsx b/web-app/src/Components/Widgets/CalendarCell.jsx index ad8355b..47ffeec 100644 --- a/web-app/src/Components/Widgets/CalendarCell.jsx +++ b/web-app/src/Components/Widgets/CalendarCell.jsx @@ -53,7 +53,7 @@ const CalendarCell = ({appointments, scheduledAppointments, onCellClick, onItemC > diff --git a/web-app/src/Redux/Slices/appointmentsSlice.js b/web-app/src/Redux/Slices/appointmentsSlice.js index 249324c..e2745b3 100644 --- a/web-app/src/Redux/Slices/appointmentsSlice.js +++ b/web-app/src/Redux/Slices/appointmentsSlice.js @@ -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; \ No newline at end of file diff --git a/web-app/src/Redux/store.js b/web-app/src/Redux/store.js index abdf18d..1da9bf6 100644 --- a/web-app/src/Redux/store.js +++ b/web-app/src/Redux/store.js @@ -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, ) ), }); diff --git a/web-app/src/Types/defaultModalPropType.js b/web-app/src/Types/defaultModalPropType.js new file mode 100644 index 0000000..d40c57b --- /dev/null +++ b/web-app/src/Types/defaultModalPropType.js @@ -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, +}) \ No newline at end of file