начал делать создание приема
This commit is contained in:
parent
b0e654a367
commit
88ee83047d
156
web-app/package-lock.json
generated
156
web-app/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
23
web-app/src/Api/appointmentTypesApi.js
Normal file
23
web-app/src/Api/appointmentTypesApi.js
Normal 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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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";
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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());
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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();
|
||||
};
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -189,8 +189,6 @@ const LensIssueFormModal = ({visible, onCancel, onSubmit}) => {
|
||||
title: 'Подтверждение', content: ConfirmStep,
|
||||
}];
|
||||
|
||||
console.log(steps[lensIssueFormModalUI.currentStep].title)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Выдача линзы пациенту"
|
||||
|
||||
@ -10,8 +10,6 @@ const useLensIssueForm = () => {
|
||||
pollingInterval: 10000,
|
||||
});
|
||||
|
||||
console.log(lenses)
|
||||
|
||||
return {
|
||||
patients,
|
||||
lenses,
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
@ -28,6 +28,7 @@ const useIssuesUI = (issues) => {
|
||||
endFilterDate,
|
||||
} = useSelector(state => state.lensIssuesUI);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
document.title = "Выдача линз";
|
||||
const cachedViewMode = getCachedInfo("viewModeIssues");
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -190,7 +190,6 @@ const PatientsPage = () => {
|
||||
visible={patientsUI.isModalVisible}
|
||||
onCancel={patientsUI.handleCloseModal}
|
||||
onSubmit={patientsData.handleModalSubmit}
|
||||
patient={patientsUI.selectedPatient}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
@ -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,
|
||||
)
|
||||
),
|
||||
});
|
||||
|
||||
8
web-app/src/Types/defaultModalPropType.js
Normal file
8
web-app/src/Types/defaultModalPropType.js
Normal 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,
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user