начал делать создание приема
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",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-quill": "^2.0.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^7.1.1",
|
"react-router-dom": "^7.1.1",
|
||||||
"validator": "^13.12.0"
|
"validator": "^13.12.0"
|
||||||
@ -1515,6 +1516,15 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.18",
|
"version": "18.3.18",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
||||||
@ -1906,7 +1916,6 @@
|
|||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||||
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.0",
|
"call-bind-apply-helpers": "^1.0.0",
|
||||||
@ -1938,7 +1947,6 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
@ -1984,6 +1992,15 @@
|
|||||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"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": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
@ -2157,7 +2194,6 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-define-property": "^1.0.0",
|
"es-define-property": "^1.0.0",
|
||||||
@ -2175,7 +2211,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
|
||||||
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
|
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"define-data-property": "^1.0.1",
|
"define-data-property": "^1.0.1",
|
||||||
@ -2660,6 +2695,18 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@ -2667,6 +2714,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/fast-json-stable-stringify": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
"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",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
||||||
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
|
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@ -2975,7 +3027,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-define-property": "^1.0.0"
|
"es-define-property": "^1.0.0"
|
||||||
@ -3110,6 +3161,22 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/is-array-buffer": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
||||||
@ -3232,7 +3299,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
|
||||||
"integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
|
"integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
@ -3337,7 +3403,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||||
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
|
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
@ -3615,6 +3680,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@ -3732,11 +3803,26 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/object-keys": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -3885,6 +3971,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
@ -4008,6 +4100,34 @@
|
|||||||
"node": ">=6"
|
"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": {
|
"node_modules/rc-cascader": {
|
||||||
"version": "3.33.1",
|
"version": "3.33.1",
|
||||||
"resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.33.1.tgz",
|
"resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.33.1.tgz",
|
||||||
@ -4651,6 +4771,21 @@
|
|||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/react-redux": {
|
||||||
"version": "9.2.0",
|
"version": "9.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||||
@ -4762,7 +4897,6 @@
|
|||||||
"version": "1.5.4",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
||||||
"integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
|
"integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.8",
|
"call-bind": "^1.0.8",
|
||||||
@ -4951,7 +5085,6 @@
|
|||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"define-data-property": "^1.1.4",
|
"define-data-property": "^1.1.4",
|
||||||
@ -4969,7 +5102,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
|
||||||
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
|
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"define-data-property": "^1.1.4",
|
"define-data-property": "^1.1.4",
|
||||||
|
|||||||
@ -22,6 +22,7 @@
|
|||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-quill": "^2.0.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^7.1.1",
|
"react-router-dom": "^7.1.1",
|
||||||
"validator": "^13.12.0"
|
"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";
|
import CONFIG from "../Core/сonfig.js";
|
||||||
|
|
||||||
|
|
||||||
export const appointmentsApi = createApi({
|
export const appointmentsApi = createApi({
|
||||||
reducerPath: 'appointmentsApi',
|
reducerPath: 'appointmentsApi',
|
||||||
baseQuery: fetchBaseQuery({
|
baseQuery: fetchBaseQuery({
|
||||||
@ -10,18 +9,36 @@ export const appointmentsApi = createApi({
|
|||||||
const token = localStorage.getItem('access_token');
|
const token = localStorage.getItem('access_token');
|
||||||
if (token) headers.set('Authorization', `Bearer ${token}`);
|
if (token) headers.set('Authorization', `Bearer ${token}`);
|
||||||
return headers;
|
return headers;
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
tagTypes: ['Appointment'],
|
tagTypes: ['Appointment'],
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
getAppointments: builder.query({
|
getAppointments: builder.query({
|
||||||
query: () => '/appointments/',
|
query: () => '/appointments/',
|
||||||
providesTags: ['Appointment'],
|
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 {
|
export const {
|
||||||
useGetAppointmentsQuery,
|
useGetAppointmentsQuery,
|
||||||
|
useCreateAppointmentMutation,
|
||||||
|
useUpdateAppointmentMutation,
|
||||||
} = appointmentsApi;
|
} = 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";
|
import CONFIG from "../Core/сonfig.js";
|
||||||
|
|
||||||
|
|
||||||
export const scheduledAppointmentsApi = createApi({
|
export const scheduledAppointmentsApi = createApi({
|
||||||
reducerPath: 'scheduledAppointmentsApi',
|
reducerPath: 'scheduledAppointmentsApi',
|
||||||
baseQuery: fetchBaseQuery({
|
baseQuery: fetchBaseQuery({
|
||||||
@ -10,16 +9,35 @@ export const scheduledAppointmentsApi = createApi({
|
|||||||
const token = localStorage.getItem('access_token');
|
const token = localStorage.getItem('access_token');
|
||||||
if (token) headers.set('Authorization', `Bearer ${token}`);
|
if (token) headers.set('Authorization', `Bearer ${token}`);
|
||||||
return headers;
|
return headers;
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
tagTypes: ['ScheduledAppointment'],
|
tagTypes: ['ScheduledAppointment'],
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
getScheduledAppointments: builder.query({
|
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 {
|
export const {
|
||||||
useGetScheduledAppointmentsQuery,
|
useGetScheduledAppointmentsQuery,
|
||||||
|
useCreateScheduledAppointmentMutation,
|
||||||
|
useUpdateScheduledAppointmentMutation,
|
||||||
} = scheduledAppointmentsApi;
|
} = scheduledAppointmentsApi;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import {Card, Modal, Popconfirm, Tooltip} from "antd";
|
import {Card, Popconfirm, Tooltip} from "antd";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import {DeleteOutlined, EditOutlined, EyeOutlined} from "@ant-design/icons";
|
import {DeleteOutlined, EditOutlined, EyeOutlined} from "@ant-design/icons";
|
||||||
import {useState} from "react";
|
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 {Splitter} from "antd";
|
||||||
import {
|
import {CalendarOutlined, TableOutlined, MenuFoldOutlined, MenuUnfoldOutlined, PlusOutlined} from "@ant-design/icons";
|
||||||
CalendarOutlined, TableOutlined, MenuFoldOutlined, MenuUnfoldOutlined,
|
|
||||||
} from "@ant-design/icons";
|
|
||||||
import AppointmentsCalendarTab from "./Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx";
|
import AppointmentsCalendarTab from "./Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx";
|
||||||
import AppointmentsTableTab from "./Components/AppointmentTableTab/AppointmentsTableTab.jsx";
|
import AppointmentsTableTab from "./Components/AppointmentTableTab/AppointmentsTableTab.jsx";
|
||||||
import useAppointmentsUI from "./useAppointmentsUI.js";
|
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 AppointmentsPage = () => {
|
||||||
const appointmentsPageUI = useAppointmentsUI();
|
const appointmentsData = useAppointments();
|
||||||
|
const appointmentsPageUI = useAppointmentsUI(appointmentsData.appointments, appointmentsData.scheduledAppointments);
|
||||||
|
|
||||||
const items = [{
|
const items = [
|
||||||
key: "1",
|
{
|
||||||
label: "Календарь приемов",
|
key: "1",
|
||||||
children: <AppointmentsCalendarTab/>,
|
label: "Календарь приемов",
|
||||||
icon: <CalendarOutlined/>,
|
children: <AppointmentsCalendarTab/>,
|
||||||
}, {
|
icon: <CalendarOutlined/>,
|
||||||
key: "2",
|
},
|
||||||
label: "Таблица приемов",
|
{
|
||||||
children: <AppointmentsTableTab/>,
|
key: "2",
|
||||||
icon: <TableOutlined/>,
|
label: "Таблица приемов",
|
||||||
},];
|
children: <AppointmentsTableTab/>,
|
||||||
|
icon: <TableOutlined/>,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (appointmentsData.isError) return (
|
||||||
|
<Result
|
||||||
|
status="error"
|
||||||
|
title="Ошибка"
|
||||||
|
subTitle="Произошла ошибка в работе страницы"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Splitter
|
{appointmentsData.isLoading ? (
|
||||||
style={appointmentsPageUI.splitterStyle}
|
<LoadingIndicator/>
|
||||||
min={200}
|
) : (
|
||||||
max={400}
|
<>
|
||||||
initial={appointmentsPageUI.siderWidth}
|
<Splitter
|
||||||
onChange={appointmentsPageUI.setSiderWidth}
|
style={appointmentsPageUI.splitterStyle}
|
||||||
>
|
min={200}
|
||||||
<Splitter.Panel
|
max={400}
|
||||||
style={appointmentsPageUI.splitterContentPanelStyle}
|
initial={appointmentsPageUI.siderWidth}
|
||||||
defaultSize="80%"
|
onChange={appointmentsPageUI.setSiderWidth}
|
||||||
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}>
|
<Splitter.Panel
|
||||||
Предстоящие события
|
style={appointmentsPageUI.splitterContentPanelStyle}
|
||||||
</Typography.Title>
|
defaultSize="80%"
|
||||||
<p>Здесь будут предстоящие приемы...</p>
|
min="25%"
|
||||||
</Splitter.Panel>
|
max="90%"
|
||||||
)}
|
>
|
||||||
</Splitter>
|
<Tabs defaultActiveKey="1" items={items}/>
|
||||||
<div
|
</Splitter.Panel>
|
||||||
style={appointmentsPageUI.siderButtonContainerStyle}
|
|
||||||
onMouseEnter={appointmentsPageUI.handleHoverSider}
|
{appointmentsPageUI.showSplitterPanel && (
|
||||||
onMouseLeave={appointmentsPageUI.handleLeaveSider}
|
<Splitter.Panel
|
||||||
>
|
style={appointmentsPageUI.splitterSiderPanelStyle}
|
||||||
<Button
|
defaultSize="20%"
|
||||||
type="primary"
|
min="20%"
|
||||||
onClick={appointmentsPageUI.handleToggleSider}
|
max="75%"
|
||||||
icon={appointmentsPageUI.collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>}
|
>
|
||||||
style={appointmentsPageUI.siderButtonStyle}
|
<Typography.Title level={3} style={appointmentsPageUI.siderTitleStyle}>
|
||||||
>
|
Предстоящие события
|
||||||
{appointmentsPageUI.siderButtonText}
|
</Typography.Title>
|
||||||
</Button>
|
{appointmentsPageUI.upcomingEvents.length ? (
|
||||||
</div>
|
<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/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 CalendarCell from "../../../../Widgets/CalendarCell.jsx";
|
||||||
import useAppointments from "../../useAppointments.js";
|
import useAppointments from "../../useAppointments.js";
|
||||||
import useAppointmentCalendarUI from "./useAppointmentCalendarUI.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 AppointmentsCalendarTab = () => {
|
||||||
const appointmentsData = useAppointments();
|
const appointmentsData = useAppointments();
|
||||||
const appointmentsCalendarUI = useAppointmentCalendarUI(appointmentsData.appointments, appointmentsData.scheduledAppointments);
|
const appointmentsCalendarUI = useAppointmentCalendarUI(
|
||||||
|
appointmentsData.appointments,
|
||||||
|
appointmentsData.scheduledAppointments
|
||||||
|
);
|
||||||
|
|
||||||
const dateCellRender = (value) => {
|
const dateCellRender = (value) => {
|
||||||
const appointmentsForDate = appointmentsCalendarUI.getAppointmentsByListAndDate(appointmentsData.appointments, value);
|
const appointmentsForDate = appointmentsCalendarUI.getAppointmentsByListAndDate(
|
||||||
const scheduledForDate = appointmentsCalendarUI.getAppointmentsByListAndDate(appointmentsData.scheduledAppointments, value);
|
appointmentsData.appointments,
|
||||||
|
value
|
||||||
|
);
|
||||||
|
const scheduledForDate = appointmentsCalendarUI.getAppointmentsByListAndDate(
|
||||||
|
appointmentsData.scheduledAppointments,
|
||||||
|
value,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CalendarCell
|
<CalendarCell
|
||||||
appointments={appointmentsForDate}
|
appointments={appointmentsForDate}
|
||||||
scheduledAppointments={scheduledForDate}
|
scheduledAppointments={scheduledForDate}
|
||||||
onCellClick={() => {
|
onCellClick={() => appointmentsCalendarUI.onSelect(value)}
|
||||||
}}
|
onItemClick={(appointment) => {
|
||||||
onItemClick={() => {
|
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 {useDispatch, useSelector} from "react-redux";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import utc from "dayjs/plugin/utc";
|
||||||
|
import timezone from "dayjs/plugin/timezone";
|
||||||
|
import {Form, Grid, notification} from "antd";
|
||||||
import {
|
import {
|
||||||
openModal,
|
closeModal,
|
||||||
|
openModal, setSelectedAppointment,
|
||||||
setSelectedAppointments,
|
setSelectedAppointments,
|
||||||
setSelectedDate
|
setSelectedDate, setSelectedScheduledAppointment,
|
||||||
} from "../../../../../Redux/Slices/appointmentsSlice.js";
|
} 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;
|
const {useBreakpoint} = Grid;
|
||||||
|
|
||||||
@ -15,9 +28,9 @@ const useAppointmentCalendarUI = (appointments, scheduledAppointments) => {
|
|||||||
modalVisible,
|
modalVisible,
|
||||||
selectedAppointments,
|
selectedAppointments,
|
||||||
selectedAppointment,
|
selectedAppointment,
|
||||||
|
selectedScheduledAppointment,
|
||||||
} = useSelector(state => state.appointmentsUI);
|
} = 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 screens = useBreakpoint();
|
||||||
const fullScreenCalendar = !screens.xs;
|
const fullScreenCalendar = !screens.xs;
|
||||||
@ -25,29 +38,114 @@ const useAppointmentCalendarUI = (appointments, scheduledAppointments) => {
|
|||||||
const calendarContainerStyle = {padding: 20};
|
const calendarContainerStyle = {padding: 20};
|
||||||
|
|
||||||
const onSelect = (date) => {
|
const onSelect = (date) => {
|
||||||
const selectedDateStr = date.format('YYYY-MM-DD');
|
const selectedDateStr = date.tz('Europe/Moscow').format('YYYY-MM-DD');
|
||||||
dispatch(setSelectedDate(selectedDateStr));
|
dispatch(setSelectedDate(selectedDateStr));
|
||||||
|
|
||||||
console.log(appointments)
|
|
||||||
const appointmentsForDate = appointments.filter(app =>
|
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 =>
|
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(setSelectedAppointments([...appointmentsForDate, ...scheduledForDate]));
|
||||||
dispatch(openModal());
|
dispatch(openModal());
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAppointmentsByListAndDate = (list, value) => {
|
const onFinish = async (values) => {
|
||||||
const date = value.format('YYYY-MM-DD');
|
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 =>
|
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 {
|
return {
|
||||||
selectedDate,
|
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 useAppointments = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [createAppointment] = useCreateAppointmentMutation();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: appointments = [],
|
data: appointments = [],
|
||||||
isLoadingAppointments,
|
isLoading: isLoadingAppointments,
|
||||||
isErrorAppointments,
|
isError: isErrorAppointments,
|
||||||
} = useGetAppointmentsQuery(undefined, {
|
} = useGetAppointmentsQuery(undefined, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 20000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: scheduledAppointments = [],
|
data: scheduledAppointments = [],
|
||||||
isLoadingScheduledAppointments,
|
isLoading: isLoadingScheduledAppointments,
|
||||||
isErrorScheduledAppointments,
|
isError: isErrorScheduledAppointments,
|
||||||
} = useGetAppointmentsQuery(undefined, {
|
} = useGetScheduledAppointmentsQuery(undefined, {
|
||||||
pollingInterval: 20000,
|
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 {
|
return {
|
||||||
|
patients,
|
||||||
appointments,
|
appointments,
|
||||||
scheduledAppointments,
|
scheduledAppointments,
|
||||||
isLoading: isLoadingAppointments || isLoadingScheduledAppointments,
|
handleSubmitModal,
|
||||||
isError: isErrorAppointments || isErrorScheduledAppointments,
|
isLoading: isLoadingAppointments || isLoadingScheduledAppointments || isLoadingPatients,
|
||||||
|
isError: isErrorAppointments || isErrorScheduledAppointments || isErrorPatients,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import {Grid} from "antd";
|
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 {useEffect, useMemo} from "react";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const {useBreakpoint} = Grid;
|
const {useBreakpoint} = Grid;
|
||||||
|
|
||||||
const useAppointmentsUI = () => {
|
const useAppointmentsUI = (appointments, scheduledAppointments) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const {
|
const {
|
||||||
collapsed,
|
collapsed,
|
||||||
siderWidth,
|
siderWidth,
|
||||||
hovered,
|
hovered,
|
||||||
|
modalVisible,
|
||||||
} = useSelector(state => state.appointmentsUI);
|
} = useSelector(state => state.appointmentsUI);
|
||||||
const screens = useBreakpoint();
|
const screens = useBreakpoint();
|
||||||
|
|
||||||
@ -36,7 +38,7 @@ const useAppointmentsUI = () => {
|
|||||||
padding: hovered ? "0 20px" : "0",
|
padding: hovered ? "0 20px" : "0",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textAlign: "left",
|
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",
|
borderRadius: "4px 0 0 4px",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,9 +46,18 @@ const useAppointmentsUI = () => {
|
|||||||
const handleHoverSider = () => dispatch(setHovered(true));
|
const handleHoverSider = () => dispatch(setHovered(true));
|
||||||
const handleLeaveSider = () => dispatch(setHovered(false));
|
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 siderButtonText = useMemo(() => hovered ? (collapsed ? "Показать предстоящие события" : "Скрыть предстоящие события") : "", [collapsed, hovered]);
|
||||||
const showSplitterPanel = useMemo(() => !collapsed && !screens.xs, [collapsed, screens]);
|
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 {
|
return {
|
||||||
collapsed,
|
collapsed,
|
||||||
siderWidth,
|
siderWidth,
|
||||||
@ -59,9 +70,14 @@ const useAppointmentsUI = () => {
|
|||||||
siderTitleStyle,
|
siderTitleStyle,
|
||||||
siderButtonContainerStyle,
|
siderButtonContainerStyle,
|
||||||
siderButtonStyle,
|
siderButtonStyle,
|
||||||
|
upcomingEvents,
|
||||||
|
modalVisible,
|
||||||
handleToggleSider,
|
handleToggleSider,
|
||||||
handleHoverSider,
|
handleHoverSider,
|
||||||
handleLeaveSider,
|
handleLeaveSider,
|
||||||
|
openCreateAppointmentModal,
|
||||||
|
handleCloseModal,
|
||||||
|
handleModalSubmit,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -189,8 +189,6 @@ const LensIssueFormModal = ({visible, onCancel, onSubmit}) => {
|
|||||||
title: 'Подтверждение', content: ConfirmStep,
|
title: 'Подтверждение', content: ConfirmStep,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
console.log(steps[lensIssueFormModalUI.currentStep].title)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Выдача линзы пациенту"
|
title="Выдача линзы пациенту"
|
||||||
|
|||||||
@ -10,8 +10,6 @@ const useLensIssueForm = () => {
|
|||||||
pollingInterval: 10000,
|
pollingInterval: 10000,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(lenses)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
patients,
|
patients,
|
||||||
lenses,
|
lenses,
|
||||||
|
|||||||
@ -6,9 +6,6 @@ import {closeModal} from "../../../Redux/Slices/lensIssuesSlice.js";
|
|||||||
|
|
||||||
const useIssues = () => {
|
const useIssues = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const {
|
|
||||||
selectedIssue,
|
|
||||||
} = useSelector(state => state.lensIssuesUI);
|
|
||||||
|
|
||||||
const {data: issues = [], isLoading, isError, error} = useGetLensIssuesQuery(undefined, {
|
const {data: issues = [], isLoading, isError, error} = useGetLensIssuesQuery(undefined, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 20000,
|
||||||
@ -39,7 +36,6 @@ const useIssues = () => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
error,
|
error,
|
||||||
selectedIssue,
|
|
||||||
handleSubmitFormModal,
|
handleSubmitFormModal,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -28,6 +28,7 @@ const useIssuesUI = (issues) => {
|
|||||||
endFilterDate,
|
endFilterDate,
|
||||||
} = useSelector(state => state.lensIssuesUI);
|
} = useSelector(state => state.lensIssuesUI);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Выдача линз";
|
document.title = "Выдача линз";
|
||||||
const cachedViewMode = getCachedInfo("viewModeIssues");
|
const cachedViewMode = getCachedInfo("viewModeIssues");
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import usePatientFormUI from "./usePatientFormUI.js";
|
|||||||
|
|
||||||
const {TextArea} = Input;
|
const {TextArea} = Input;
|
||||||
|
|
||||||
const PatientFormModal = ({visible, onCancel, onSubmit, patient}) => {
|
const PatientFormModal = ({visible, onCancel, onSubmit}) => {
|
||||||
const patientFormModalUI = usePatientFormUI(visible, onCancel, onSubmit, patient);
|
const patientFormModalUI = usePatientFormUI(visible, onCancel, onSubmit);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@ -2,28 +2,31 @@ import {Form, notification} from "antd";
|
|||||||
import {useEffect} from "react";
|
import {useEffect} from "react";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import validator from "validator";
|
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();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
if (patient) {
|
if (selectedPatient) {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
...patient,
|
...selectedPatient,
|
||||||
birthday: patient.birthday ? dayjs(patient.birthday, "YYYY-MM-DD") : null,
|
birthday: selectedPatient.birthday ? dayjs(selectedPatient.birthday, "YYYY-MM-DD") : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [visible, patient, form]);
|
}, [visible, selectedPatient, form]);
|
||||||
|
|
||||||
const modalStyle = {
|
const modalStyle = {
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
marginBottom: 50,
|
marginBottom: 50,
|
||||||
};
|
};
|
||||||
const modalTitle = patient ? "Редактировать пациента" : "Добавить пациента";
|
const modalTitle = selectedPatient ? "Редактировать пациента" : "Добавить пациента";
|
||||||
|
|
||||||
const emailValidator = (_, value) => {
|
const emailValidator = (_, value) => {
|
||||||
if (value && !validator.isEmail(value)) {
|
if (value && !validator.isEmail(value)) {
|
||||||
|
|||||||
@ -190,7 +190,6 @@ const PatientsPage = () => {
|
|||||||
visible={patientsUI.isModalVisible}
|
visible={patientsUI.isModalVisible}
|
||||||
onCancel={patientsUI.handleCloseModal}
|
onCancel={patientsUI.handleCloseModal}
|
||||||
onSubmit={patientsData.handleModalSubmit}
|
onSubmit={patientsData.handleModalSubmit}
|
||||||
patient={patientsUI.selectedPatient}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -18,7 +18,6 @@ const usePatientsUI = (patients) => {
|
|||||||
searchText,
|
searchText,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
viewMode,
|
viewMode,
|
||||||
selectedPatient,
|
|
||||||
isModalVisible,
|
isModalVisible,
|
||||||
currentPage,
|
currentPage,
|
||||||
pageSize,
|
pageSize,
|
||||||
@ -87,7 +86,6 @@ const usePatientsUI = (patients) => {
|
|||||||
searchText,
|
searchText,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
viewMode,
|
viewMode,
|
||||||
selectedPatient,
|
|
||||||
isModalVisible,
|
isModalVisible,
|
||||||
currentPage,
|
currentPage,
|
||||||
pageSize,
|
pageSize,
|
||||||
|
|||||||
@ -53,7 +53,7 @@ const CalendarCell = ({appointments, scheduledAppointments, onCellClick, onItemC
|
|||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
status="success"
|
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>
|
</Tag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@ -10,6 +10,8 @@ const initialState = {
|
|||||||
modalVisible: false,
|
modalVisible: false,
|
||||||
selectedAppointments: [],
|
selectedAppointments: [],
|
||||||
selectedAppointment: null,
|
selectedAppointment: null,
|
||||||
|
scheduledAppointments: [],
|
||||||
|
selectedScheduledAppointment: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const appointmentsSlice = createSlice({
|
const appointmentsSlice = createSlice({
|
||||||
@ -40,6 +42,12 @@ const appointmentsSlice = createSlice({
|
|||||||
setSelectedAppointment: (state, action) => {
|
setSelectedAppointment: (state, action) => {
|
||||||
state.selectedAppointment = action.payload;
|
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,
|
closeModal,
|
||||||
setSelectedAppointments,
|
setSelectedAppointments,
|
||||||
setSelectedAppointment,
|
setSelectedAppointment,
|
||||||
|
setSelectedScheduledAppointment,
|
||||||
|
setScheduledAppointments,
|
||||||
} = appointmentsSlice.actions;
|
} = appointmentsSlice.actions;
|
||||||
|
|
||||||
export default appointmentsSlice.reducer;
|
export default appointmentsSlice.reducer;
|
||||||
@ -11,6 +11,8 @@ import lensIssuesReducer from "./Slices/lensIssuesSlice.js";
|
|||||||
import {lensTypesApi} from "../Api/lensTypesApi.js";
|
import {lensTypesApi} from "../Api/lensTypesApi.js";
|
||||||
import {appointmentsApi} from "../Api/appointmentsApi.js";
|
import {appointmentsApi} from "../Api/appointmentsApi.js";
|
||||||
import appointmentsReducer from "./Slices/appointmentsSlice.js";
|
import appointmentsReducer from "./Slices/appointmentsSlice.js";
|
||||||
|
import {scheduledAppointmentsApi} from "../Api/scheduledAppointmentsApi.js";
|
||||||
|
import {appointmentTypesApi} from "../Api/appointmentTypesApi.js";
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
@ -32,6 +34,10 @@ export const store = configureStore({
|
|||||||
|
|
||||||
[appointmentsApi.reducerPath]: appointmentsApi.reducer,
|
[appointmentsApi.reducerPath]: appointmentsApi.reducer,
|
||||||
appointmentsUI: appointmentsReducer,
|
appointmentsUI: appointmentsReducer,
|
||||||
|
|
||||||
|
[scheduledAppointmentsApi.reducerPath]: scheduledAppointmentsApi.reducer,
|
||||||
|
|
||||||
|
[appointmentTypesApi.reducerPath]: appointmentTypesApi.reducer,
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) => (
|
middleware: (getDefaultMiddleware) => (
|
||||||
getDefaultMiddleware().concat(
|
getDefaultMiddleware().concat(
|
||||||
@ -42,6 +48,8 @@ export const store = configureStore({
|
|||||||
lensTypesApi.middleware,
|
lensTypesApi.middleware,
|
||||||
lensIssuesApi.middleware,
|
lensIssuesApi.middleware,
|
||||||
appointmentsApi.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