From 60f1ab9f4acda0e681e07cf9bc98052d998e1d5f Mon Sep 17 00:00:00 2001 From: Bohdan Forostianyi Date: Thu, 17 Dec 2020 05:40:50 +0100 Subject: [PATCH 1/3] TASK-141 Adds pouchdb --- docs/example - december.xlsx | Bin 0 -> 17017 bytes package-lock.json | 228 +++++++++++++++++- package.json | 2 + src/api/local-storage-provider.model.ts | 88 +++---- src/api/persistance-store.model.ts | 26 +- src/app.tsx | 2 +- src/components/schedule-page.component.tsx | 35 --- .../edit-page/edit-page-toolbar.component.tsx | 93 +++++++ .../edit-page/schedule-edit.page.tsx | 18 ++ .../schedule-edit-mode.component.tsx | 103 -------- .../schedule-page/schedule-page.component.tsx | 51 ++++ .../schedule-view-only-mode.component.tsx | 33 --- .../table/legend/legend-component.tsx | 7 - .../schedule-parts/base-row.component.tsx | 2 +- .../table/schedule/schedule.component.tsx | 190 +++++++-------- .../shifts-section.component.tsx | 2 +- .../table/schedule/use-schedule-state.ts | 46 ++-- .../view-only-page/view-only-toolbar.tsx | 21 ++ .../view-only-page/view-only.page.tsx | 18 ++ .../timetable/timetable-row.component.tsx | 2 +- .../timetable/timetable-section.component.tsx | 21 +- src/helpers/verbose-date.helper.ts | 4 +- src/logic/schedule-logic/month-info.logic.ts | 4 + src/logic/schedule-logic/schedule.logic.ts | 30 ++- 24 files changed, 648 insertions(+), 378 deletions(-) create mode 100644 docs/example - december.xlsx delete mode 100644 src/components/schedule-page.component.tsx create mode 100644 src/components/schedule-page/edit-page/edit-page-toolbar.component.tsx create mode 100644 src/components/schedule-page/edit-page/schedule-edit.page.tsx delete mode 100644 src/components/schedule-page/schedule-edit-mode.component.tsx create mode 100644 src/components/schedule-page/schedule-page.component.tsx delete mode 100644 src/components/schedule-page/schedule-view-only-mode.component.tsx delete mode 100644 src/components/schedule-page/table/legend/legend-component.tsx create mode 100644 src/components/schedule-page/view-only-page/view-only-toolbar.tsx create mode 100644 src/components/schedule-page/view-only-page/view-only.page.tsx diff --git a/docs/example - december.xlsx b/docs/example - december.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..34131e788a0edc4a03aa02d0a639f5690e72358e GIT binary patch literal 17017 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 literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index 7de8e2c9b..460bc6b04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2204,8 +2204,7 @@ "@types/debug": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", - "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", - "dev": true + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==" }, "@types/eslint-visitor-keys": { "version": "1.0.0", @@ -2282,11 +2281,196 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.8.tgz", "integrity": "sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw==" }, + "@types/node-fetch": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", + "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "@types/pouchdb": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@types/pouchdb/-/pouchdb-6.4.0.tgz", + "integrity": "sha512-eGCpX+NXhd5VLJuJMzwe3L79fa9+IDTrAG3CPaf4s/31PD56hOrhDJTSmRELSXuiqXr6+OHzzP0PldSaWsFt7w==", + "requires": { + "@types/pouchdb-adapter-cordova-sqlite": "*", + "@types/pouchdb-adapter-fruitdown": "*", + "@types/pouchdb-adapter-http": "*", + "@types/pouchdb-adapter-idb": "*", + "@types/pouchdb-adapter-leveldb": "*", + "@types/pouchdb-adapter-localstorage": "*", + "@types/pouchdb-adapter-memory": "*", + "@types/pouchdb-adapter-node-websql": "*", + "@types/pouchdb-adapter-websql": "*", + "@types/pouchdb-browser": "*", + "@types/pouchdb-core": "*", + "@types/pouchdb-http": "*", + "@types/pouchdb-mapreduce": "*", + "@types/pouchdb-node": "*", + "@types/pouchdb-replication": "*" + } + }, + "@types/pouchdb-adapter-cordova-sqlite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-cordova-sqlite/-/pouchdb-adapter-cordova-sqlite-1.0.0.tgz", + "integrity": "sha512-NsqpEYKunBS/BPvttlOVQ5Me6LdU6UYZB0Qak3XS/AvLeIRdF61MZ/czSuL/ozydYr6bikewt6dvlpCK1HWG9Q==", + "requires": { + "@types/pouchdb-core": "*" + } + }, + "@types/pouchdb-adapter-fruitdown": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-fruitdown/-/pouchdb-adapter-fruitdown-6.1.3.tgz", + "integrity": "sha512-Wz1Z1JLOW1hgmFQjqnSkmyyfH7by/iWb4abKn684WMvQfmxx6BxKJpJ4+eulkVPQzzgMMSgU1MpnQOm9FgRkbw==", + "requires": { + "@types/pouchdb-core": "*" + } + }, + "@types/pouchdb-adapter-http": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-http/-/pouchdb-adapter-http-6.1.3.tgz", + "integrity": "sha512-9Z4TLbF/KJWy/D2sWRPBA+RNU0odQimfdvlDX+EY7rGcd3aVoH8qjD/X0Xcd/0dfBH5pKrNIMFFQgW/TylRCmA==", + "requires": { + "@types/pouchdb-core": "*" + } + }, + "@types/pouchdb-adapter-idb": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-idb/-/pouchdb-adapter-idb-6.1.3.tgz", + "integrity": "sha512-K4G9pmHkR2JyL8d6cllIEix2dtQFVIJyDcgoT7ctrbIyyhT4kRjieGc3O7tzIhm1bv7W2qz1aResO9lq7qjKVQ==", + "requires": { + "@types/pouchdb-core": "*" + } + }, + "@types/pouchdb-adapter-leveldb": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-leveldb/-/pouchdb-adapter-leveldb-6.1.3.tgz", + "integrity": "sha512-ex8NFqQGFwEpFi7AaZ5YofmuemfZNsL3nTFZBUCAKYMBkazQij1pe2ILLStSvJr0XS0qxgXjCEW19T5Wqiiskg==", + "requires": { + "@types/pouchdb-core": "*" + } + }, + "@types/pouchdb-adapter-localstorage": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-localstorage/-/pouchdb-adapter-localstorage-6.1.3.tgz", + "integrity": "sha512-oor040tye1KKiGLWYtIy7rRT7C2yoyX3Tf6elEJRpjOA7Ja/H8lKc4LaSh9ATbptIcES6MRqZDxtp7ly9hsW3Q==", + "requires": { + "@types/pouchdb-core": "*" + } + }, + "@types/pouchdb-adapter-memory": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-memory/-/pouchdb-adapter-memory-6.1.3.tgz", + "integrity": "sha512-gVbsIMzDzgZYThFVT4eVNsmuZwVm/4jDxP1sjlgc3qtDIxbtBhGgyNfcskwwz9Zu5Lv1avkDsIWvcxQhnvRlHg==", + "requires": { + "@types/pouchdb-core": "*" + } + }, + "@types/pouchdb-adapter-node-websql": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-node-websql/-/pouchdb-adapter-node-websql-6.1.3.tgz", + "integrity": "sha512-F/P+os6Jsa7CgHtH64+Z0HfwIcj0hIRB5z8gNhF7L7dxPWoAfkopK5H2gydrP3sQrlGyN4WInF+UJW/Zu1+FKg==", + "requires": { + "@types/pouchdb-adapter-websql": "*", + "@types/pouchdb-core": "*" + } + }, + "@types/pouchdb-adapter-websql": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/pouchdb-adapter-websql/-/pouchdb-adapter-websql-6.1.3.tgz", + "integrity": "sha512-0AsCWnliwg/3PKj5NAoFuzpnMQKXGBOl+6q8aNxK3N9Tq3SbV91QhgW/mdJsOdqSOw0EBudkGdE6/CZlrgeLpw==", + "requires": { + "@types/pouchdb-core": "*" + } + }, + "@types/pouchdb-browser": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/pouchdb-browser/-/pouchdb-browser-6.1.3.tgz", + "integrity": "sha512-EdYowrWxW9SWBMX/rux2eq7dbHi5Zeyzz+FF/IAsgQKnUxgeCO5VO2j4zTzos0SDyJvAQU+EYRc11r7xGn5tvA==", + "requires": { + "@types/pouchdb-adapter-http": "*", + "@types/pouchdb-adapter-idb": "*", + "@types/pouchdb-adapter-websql": "*", + "@types/pouchdb-core": "*", + "@types/pouchdb-mapreduce": "*", + "@types/pouchdb-replication": "*" + } + }, + "@types/pouchdb-core": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/pouchdb-core/-/pouchdb-core-7.0.6.tgz", + "integrity": "sha512-MCTtOA3buNN+YVkCWFaojWzP6SsESLRp5uXtXcZE8aUm8RNM/hrVun+RVmzP4NTIGBjKQgO9U9X/bTd9k0jsXA==", + "requires": { + "@types/debug": "*", + "@types/node-fetch": "*", + "@types/pouchdb-find": "*" + } + }, + "@types/pouchdb-find": { + "version": "6.3.6", + "resolved": "https://registry.npmjs.org/@types/pouchdb-find/-/pouchdb-find-6.3.6.tgz", + "integrity": "sha512-qXgkYfmwUIMCtFcX959ywYyFYJp23Er3btfWNwm1wyYpPK9uuJD8Zh7OmcyFLzWKZG7c8eLHVvGGOp4NysHjDg==", + "requires": { + "@types/pouchdb-core": "*" + } + }, + "@types/pouchdb-http": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/pouchdb-http/-/pouchdb-http-6.1.3.tgz", + "integrity": "sha512-0e9E5SqNOyPl/3FnEIbENssB4FlJsNYuOy131nxrZk36S+y1R/6qO7ZVRypWpGTqBWSuVd7gCsq2UDwO/285+w==", + "requires": { + "@types/pouchdb-adapter-http": "*", + "@types/pouchdb-core": "*" + } + }, + "@types/pouchdb-mapreduce": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/pouchdb-mapreduce/-/pouchdb-mapreduce-6.1.4.tgz", + "integrity": "sha512-c8cZ8E9zEl7ZMcrE5jaF2qOm+cxQ01+B0NG9oGHVX3Mj5G8m4oIBAUELJmJw9LvEq3r9nXXn6g9TvvN8NiRalg==", + "requires": { + "@types/pouchdb-core": "*" + } + }, + "@types/pouchdb-node": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@types/pouchdb-node/-/pouchdb-node-6.1.3.tgz", + "integrity": "sha512-rZb3eWGLKJo4LBhUIJLofyoric/5wKYI6HVUld8CfDWeVZgYVUJ/GEw/vXuqXsbgbsaNyk6XeU9461ULBt+0qg==", + "requires": { + "@types/pouchdb-adapter-http": "*", + "@types/pouchdb-adapter-leveldb": "*", + "@types/pouchdb-core": "*", + "@types/pouchdb-mapreduce": "*", + "@types/pouchdb-replication": "*" + } + }, + "@types/pouchdb-replication": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@types/pouchdb-replication/-/pouchdb-replication-6.4.2.tgz", + "integrity": "sha512-BbuwkCv6nu8RUVjymUvhSw/Oo+VOCWyUpio3ujOoxhVdef/JZ5AUjsotgKWHiG0OtkZ8O5oCbAx5uH1Zf6w9XA==", + "requires": { + "@types/pouchdb-core": "*", + "@types/pouchdb-find": "*" + } + }, "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", @@ -3081,6 +3265,11 @@ "sprintf-js": "~1.0.2" } }, + "argsarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/argsarray/-/argsarray-0.0.1.tgz", + "integrity": "sha1-bnIHtOzbObCviDA/pa4ivajfYcs=" + }, "aria-query": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", @@ -14458,6 +14647,31 @@ "uniq": "^1.0.1" } }, + "pouchdb-browser": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/pouchdb-browser/-/pouchdb-browser-7.2.2.tgz", + "integrity": "sha512-90pkngk9/F95knUwOhGQ0xuNzG51uIecF5kD5AfNdhqqrV9wbngz+I3QA7u/9eVyZRVyOrTrT3oaKcNqAB7Brg==", + "requires": { + "argsarray": "0.0.1", + "immediate": "3.3.0", + "inherits": "2.0.4", + "spark-md5": "3.0.1", + "uuid": "8.1.0", + "vuvuzela": "1.0.3" + }, + "dependencies": { + "immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" + }, + "uuid": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz", + "integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==" + } + } + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -16504,6 +16718,11 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, + "spark-md5": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.1.tgz", + "integrity": "sha512-0tF3AGSD1ppQeuffsLDIOWlKUd3lS92tFxcsrh5Pe3ZphhnoK+oXIBTzOAThZCiuINZLvpiLH/1VS1/ANEJVig==" + }, "spawn-command": { "version": "0.0.2-1", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", @@ -18086,6 +18305,11 @@ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, + "vuvuzela": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vuvuzela/-/vuvuzela-1.0.3.tgz", + "integrity": "sha1-O+FF5YJxxzylUnndhR8SpoIRSws=" + }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", diff --git a/package.json b/package.json index 13a5b589b..157d4bbfd 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", + "@types/pouchdb": "^6.4.0", "axios": "^0.20.0", "classnames": "^2.2.6", "concurrently": "^5.3.0", @@ -35,6 +36,7 @@ "file-saver": "^2.0.2", "fontsource-roboto": "^3.0.3", "node-sass": "^4.14.1", + "pouchdb-browser": "^7.2.2", "react": "^16.13.1", "react-datasheet": "^1.4.8", "react-dnd": "^11.1.3", diff --git a/src/api/local-storage-provider.model.ts b/src/api/local-storage-provider.model.ts index d4195d327..ac6c0edbc 100644 --- a/src/api/local-storage-provider.model.ts +++ b/src/api/local-storage-provider.model.ts @@ -1,5 +1,5 @@ import { ScheduleDataModel } from "../common-models/schedule-data.model"; -import { ShiftInfoModel, ShiftModel } from "../common-models/shift-info.model"; +import { ShiftModel } from "../common-models/shift-info.model"; import { WorkerInfoModel, WorkersInfoModel } from "../common-models/worker-info.model"; import { ScheduleDataActionType } from "../state/reducers/schedule-data.reducer"; import { @@ -8,74 +8,74 @@ import { ScheduleRevision, ThunkFunction, ScheduleKey, - ScheduleRecord, + RevisionType, } from "./persistance-store.model"; -/*eslint-disable @typescript-eslint/no-unused-vars*/ +import PouchDB from "pouchdb-browser"; -export class LocalStorageProvider implements PersistanceStoreProvider { - private sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); +/*eslint-disable @typescript-eslint/camelcase */ +export class LocalStorageProvider extends PersistanceStoreProvider { + private storage: PouchDB.Database; + + constructor() { + super(); + this.storage = new PouchDB("nurse-scheduling"); } - saveScheduleRevision(revision: ScheduleRevision): ThunkFunction { + + saveScheduleRevision( + type: RevisionType, + schedule: ScheduleDataModel + ): ThunkFunction { return async (dispatch, getState): Promise => { - await this.sleep(100); - const revisions = JSON.parse(localStorage.getItem("revisions") || "[]"); - localStorage.setItem("currentSchedule", JSON.stringify([...revisions, revision])); + const id = this.getScheduleId(schedule); + let revision = ""; + try { + const document = await this.storage.get(id); + revision = document._rev; + } catch {} + this.storage.put({ + _rev: revision, + _id: id, + data: schedule, + revisionType: type, + }); dispatch({ type: ScheduleDataActionType.ADD_NEW, - payload: revision.data, + payload: schedule, }); }; } + getScheduleRevision(filter: RevisionFilter): ThunkFunction { return async (dispatch, getState): Promise => { - await this.sleep(100); - const revisions: ScheduleRecord[] = JSON.parse(localStorage.getItem("revisions") || "[]"); - const result = revisions.find( - (r) => - r.validityPeriod.month === filter.validityPeriod.month && - r.validityPeriod.year === filter.validityPeriod.year - ); - if (!result) { + const revisions = await this.storage.allDocs({ include_docs: true }); + const result = revisions.rows.find((r) => { + const { year, month_number } = r.doc?.data.schedule_info ?? {}; + return ( + month_number === filter.validityPeriod.month && + year === filter.validityPeriod.year && + r.doc?.revisionType === filter.revisionType + ); + }); + if (!result?.doc) { return; } - dispatch({ type: ScheduleDataActionType.ADD_NEW, - payload: - filter.revisionType === "primary" - ? result.primaryRevision.data - : result.actualRevision.data, + payload: result.doc.data, }); }; } addNewWorker(worker: WorkerInfoModel): ThunkFunction { - return async (dispatch, getState): Promise => { - await this.sleep(100); - const workers: WorkerInfoModel[] = JSON.parse(localStorage.getItem("workers") || "[]"); - localStorage.setItem("workers", JSON.stringify([...workers, worker])); - }; + throw Error("Not implemented method"); } getWorkers(period: ScheduleKey): ThunkFunction { - return async (dispatch, getState): Promise => { - await this.sleep(100); - const workers: WorkerInfoModel[] = JSON.parse(localStorage.getItem("workers") || "[]"); - const workersInPeriod = workers; - }; + throw Error("Not implemented method"); } addNewShift(shift: ShiftModel): ThunkFunction { - return async (dispatch, getState): Promise => { - await this.sleep(100); - const shifts: ShiftInfoModel[] = JSON.parse(localStorage.getItem("shifts") || "[]"); - localStorage.setItem("shifts", JSON.stringify([...shifts, shift])); - }; + throw Error("Not implemented method"); } getShifts(period: ScheduleKey): ThunkFunction { - return async (dispatch, getState): Promise => { - await this.sleep(100); - const shifts: ShiftModel[] = JSON.parse(localStorage.getItem("shifts") || "[]"); - const shiftInPeriod = shifts; - }; + throw Error("Not implemented method"); } } diff --git a/src/api/persistance-store.model.ts b/src/api/persistance-store.model.ts index cec74c8ba..c05919c5f 100644 --- a/src/api/persistance-store.model.ts +++ b/src/api/persistance-store.model.ts @@ -5,6 +5,8 @@ import { WorkerInfoModel, WorkersInfoModel } from "../common-models/worker-info. import { ActionModel } from "../state/models/action.model"; import { ApplicationStateModel } from "../state/models/application-state.model"; +/*eslint-disable @typescript-eslint/camelcase */ + export type ThunkFunction = ( dispatch: Dispatch>, getState: () => ApplicationStateModel @@ -15,13 +17,14 @@ export interface ScheduleKey { year: number; } -type RevisionType = "primary" | "actual"; +export type RevisionType = "primary" | "actual"; export interface RevisionFilter { revisionType: RevisionType; validityPeriod: ScheduleKey; } export interface ScheduleRevision { + _id: string; revisionType: RevisionType; data: ScheduleDataModel; } @@ -32,11 +35,18 @@ export interface ScheduleRecord { validityPeriod: ScheduleKey; workersInfo: WorkerInfoModel[]; } -export interface PersistanceStoreProvider { - saveScheduleRevision(schedule: ScheduleRevision): ThunkFunction; - getScheduleRevision(filter: RevisionFilter): ThunkFunction; - addNewWorker(worker: WorkerInfoModel): ThunkFunction; - getWorkers(period: ScheduleKey): ThunkFunction; - addNewShift(shift: ShiftModel): ThunkFunction; - getShifts(period: ScheduleKey): ThunkFunction; +export abstract class PersistanceStoreProvider { + abstract saveScheduleRevision( + type: RevisionType, + schedule: ScheduleDataModel + ): ThunkFunction; + abstract getScheduleRevision(filter: RevisionFilter): ThunkFunction; + abstract addNewWorker(worker: WorkerInfoModel): ThunkFunction; + abstract getWorkers(period: ScheduleKey): ThunkFunction; + abstract addNewShift(shift: ShiftModel): ThunkFunction; + abstract getShifts(period: ScheduleKey): ThunkFunction; + protected getScheduleId(schedule: ScheduleDataModel): string { + const { month_number, year } = schedule.schedule_info; + return `${month_number}_${year}`; + } } diff --git a/src/app.tsx b/src/app.tsx index 3bbe4531c..e88a4ba13 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import { CustomGlobalHotKeys, HeaderComponent } from "./components/common-components"; -import { SchedulePage } from "./components/schedule-page.component"; +import { SchedulePage } from "./components/schedule-page/schedule-page.component"; import { useDispatch } from "react-redux"; import schedule from "./assets/devMode/schedule.js"; import { ScheduleDataActionType } from "./state/reducers/schedule-data.reducer"; diff --git a/src/components/schedule-page.component.tsx b/src/components/schedule-page.component.tsx deleted file mode 100644 index dde921a76..000000000 --- a/src/components/schedule-page.component.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from "react"; -import { Route, Switch } from "react-router-dom"; -import ScheduleViewOnlyMode from "./schedule-page/schedule-view-only-mode.component"; -import { ScheduleEditMode } from "./schedule-page/schedule-edit-mode.component"; - -interface SchedulePageOptions { - editModeHandler: (editMode: boolean) => void; -} - -export function SchedulePage(props: SchedulePageOptions): JSX.Element { - function ViewOnly(): JSX.Element { - return ( - <> - props.editModeHandler(true)} /> - - ); - } - - function Edit(): JSX.Element { - return ( - <> - props.editModeHandler(false)} /> - - ); - } - - return ( -
- - - - -
- ); -} diff --git a/src/components/schedule-page/edit-page/edit-page-toolbar.component.tsx b/src/components/schedule-page/edit-page/edit-page-toolbar.component.tsx new file mode 100644 index 000000000..614096ad9 --- /dev/null +++ b/src/components/schedule-page/edit-page/edit-page-toolbar.component.tsx @@ -0,0 +1,93 @@ +import { Link } from "react-router-dom"; +import React, { useContext } from "react"; +import { useDispatch } from "react-redux"; +import { ActionCreators as UndoActionCreators } from "redux-undo"; +import backend from "../../../api/backend"; +import { ScheduleError } from "../../../common-models/schedule-error.model"; +import { ActionModel } from "../../../state/models/action.model"; +import { ScheduleErrorActionType } from "../../../state/reducers/schedule-errors.reducer"; +import { Button } from "../../common-components"; +import ValidationDrawerComponent from "../validation-drawer/validation-drawer.component"; +import UndoIcon from "@material-ui/icons/Undo"; +import RedoIcon from "@material-ui/icons/Redo"; +import { ScheduleLogicContext } from "../table/schedule/use-schedule-state"; + +interface EditPageToolbarOptions { + closeEdit: () => void; +} + +export function EditPageToolbar({ closeEdit }: EditPageToolbarOptions): JSX.Element { + const { logic: scheduleLogic } = useContext(ScheduleLogicContext); + const dispatcher = useDispatch(); + async function updateScheduleErrors(): Promise { + const schedule = scheduleLogic?.schedule.getDataModel(); + if (schedule) { + let response: ScheduleError[]; + try { + response = await backend.getErrors(schedule); + } catch { + response = []; + } + dispatcher({ + type: ScheduleErrorActionType.UPDATE, + payload: response, + } as ActionModel); + } + } + + return ( +
+
+ + + + +
+

Tryb edycji aktywny

+
+ + + + + +
+ + + + + + +
+
+ ); +} diff --git a/src/components/schedule-page/edit-page/schedule-edit.page.tsx b/src/components/schedule-page/edit-page/schedule-edit.page.tsx new file mode 100644 index 000000000..1a16e39e3 --- /dev/null +++ b/src/components/schedule-page/edit-page/schedule-edit.page.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import { ScheduleComponent } from "../table/schedule/schedule.component"; +import { EditPageToolbar } from "./edit-page-toolbar.component"; + +interface ScheduleEditPageOptions { + closeEdit: () => void; +} + +export function ScheduleEditPage(options: ScheduleEditPageOptions): JSX.Element { + return ( +
+ +
+ +
+
+ ); +} diff --git a/src/components/schedule-page/schedule-edit-mode.component.tsx b/src/components/schedule-page/schedule-edit-mode.component.tsx deleted file mode 100644 index b172eab27..000000000 --- a/src/components/schedule-page/schedule-edit-mode.component.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React from "react"; -import { Link } from "react-router-dom"; -import { Button } from "../common-components"; -import ValidationDrawerComponent from "./validation-drawer/validation-drawer.component"; -import backend from "../../api/backend"; -import { ScheduleErrorActionType } from "../../state/reducers/schedule-errors.reducer"; -import { ActionModel } from "../../state/models/action.model"; -import { ScheduleError } from "../../common-models/schedule-error.model"; -import { useDispatch, useSelector } from "react-redux"; -import { ApplicationStateModel } from "../../state/models/application-state.model"; -import { ScheduleComponent } from "./table/schedule/schedule.component"; -import UndoIcon from "@material-ui/icons/Undo"; -import RedoIcon from "@material-ui/icons/Redo"; -import { ActionCreators as UndoActionCreators } from "redux-undo"; - -interface ScheduleEditModeOptions { - closeEdit: () => void; -} - -export function ScheduleEditMode(props: ScheduleEditModeOptions): JSX.Element { - const schedule = useSelector((state: ApplicationStateModel) => state.scheduleData?.present); - const dispatcher = useDispatch(); - - async function updateScheduleErrors(): Promise { - if (schedule) { - let response; - - try { - response = await backend.getErrors(schedule); - } catch { - response = []; - } - - dispatcher({ - type: ScheduleErrorActionType.UPDATE, - payload: response, - } as ActionModel); - } - } - - function unDo(): void { - if (schedule) { - dispatcher(UndoActionCreators.undo()); - } - } - - function reDo(): void { - if (schedule) { - dispatcher(UndoActionCreators.redo()); - } - } - - return ( -
-
-
- - - - -
-

Tryb edycji aktywny

-
- - - - - -
- - - - - - -
-
-
- -
-
- ); -} diff --git a/src/components/schedule-page/schedule-page.component.tsx b/src/components/schedule-page/schedule-page.component.tsx new file mode 100644 index 000000000..82e07baf4 --- /dev/null +++ b/src/components/schedule-page/schedule-page.component.tsx @@ -0,0 +1,51 @@ +import React, { useEffect } from "react"; +import { useSelector } from "react-redux"; +import { Route, Switch } from "react-router-dom"; +import { ApplicationStateModel } from "../../state/models/application-state.model"; +import { ScheduleEditPage } from "./edit-page/schedule-edit.page"; +import { ScheduleLogicContext, useScheduleState } from "./table/schedule/use-schedule-state"; +import { ScheduleViewOnlyPage } from "./view-only-page/view-only.page"; + +interface SchedulePageOptions { + editModeHandler: (editMode: boolean) => void; +} + +export function SchedulePage(props: SchedulePageOptions): JSX.Element { + const scheduleModel = useSelector((state: ApplicationStateModel) => state.scheduleData.present); + const { scheduleLogic, setNewSchedule, scheduleLocalState } = useScheduleState(scheduleModel); + useEffect(() => { + setNewSchedule(scheduleModel); + }, [scheduleModel, setNewSchedule]); + + function ViewOnly(): JSX.Element { + return ( + <> + props.editModeHandler(true)} /> + + ); + } + + function Edit(): JSX.Element { + return ( + <> + props.editModeHandler(false)} /> + + ); + } + + return ( + +
+ + + + +
+
+ ); +} diff --git a/src/components/schedule-page/schedule-view-only-mode.component.tsx b/src/components/schedule-page/schedule-view-only-mode.component.tsx deleted file mode 100644 index df629b87b..000000000 --- a/src/components/schedule-page/schedule-view-only-mode.component.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from "react"; -import { ImportButtonsComponent } from "./import-buttons/import-buttons.component"; -import { Button } from "../common-components"; -import { ScheduleComponent } from "./table/schedule/schedule.component"; -import { Link } from "react-router-dom"; - -interface PropsType { - openEdit: () => void; -} - -export default function ScheduleViewOnlyMode(props: PropsType): JSX.Element { - return ( - <> -
-
- - - - -
-
- -
- - ); -} diff --git a/src/components/schedule-page/table/legend/legend-component.tsx b/src/components/schedule-page/table/legend/legend-component.tsx deleted file mode 100644 index 8d7e29cf5..000000000 --- a/src/components/schedule-page/table/legend/legend-component.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; -function LegendComponent(): JSX.Element { - // in future here will be added some more logic: - // for example editing of colors for shifts - return ; -} -export default LegendComponent; diff --git a/src/components/schedule-page/table/schedule/schedule-parts/base-row.component.tsx b/src/components/schedule-page/table/schedule/schedule-parts/base-row.component.tsx index 8cfc000f0..61b68b9a5 100644 --- a/src/components/schedule-page/table/schedule/schedule-parts/base-row.component.tsx +++ b/src/components/schedule-page/table/schedule/schedule-parts/base-row.component.tsx @@ -41,7 +41,7 @@ export function BaseRowComponentF({ selection = [], isEditable = true, }: BaseRowOptions): JSX.Element { - const scheduleLogic = useContext(ScheduleLogicContext); + const { logic: scheduleLogic } = useContext(ScheduleLogicContext); const verboseDates = scheduleLogic?.sections.Metadata?.verboseDates; const currMonthNumber = scheduleLogic?.sections.Metadata.monthNumber; const numberOfDays = verboseDates?.length; diff --git a/src/components/schedule-page/table/schedule/schedule.component.tsx b/src/components/schedule-page/table/schedule/schedule.component.tsx index 182673638..44a73328e 100644 --- a/src/components/schedule-page/table/schedule/schedule.component.tsx +++ b/src/components/schedule-page/table/schedule/schedule.component.tsx @@ -1,9 +1,7 @@ -import React, { useEffect } from "react"; -import { useSelector } from "react-redux"; -import { ApplicationStateModel } from "../../../../state/models/application-state.model"; +import React, { useContext } from "react"; import { WorkerType } from "../../../../common-models/worker-info.model"; import { ShiftsSectionComponent } from "./sections/shifts-section/shifts-section.component"; -import { ScheduleLogicContext, useScheduleState } from "./use-schedule-state"; +import { ScheduleLogicContext } from "./use-schedule-state"; import { FoundationInfoComponent } from "./sections/foundation-info-section/foundation-info.component"; import { TimeTableComponent } from "../../../timetable/timetable.component"; import { NameTableComponent } from "../../../namestable/nametable.component"; @@ -12,13 +10,7 @@ import { TableMiddleLine } from "../../table-middleline"; import { OvertimeHeaderComponent } from "../../../overtime-header-table/overtime-header.component"; export function ScheduleComponent(): JSX.Element { - const scheduleModel = useSelector((state: ApplicationStateModel) => state.scheduleData.present); - - const { scheduleLogic, scheduleLocalState, setNewSchedule } = useScheduleState(scheduleModel); - - useEffect(() => { - setNewSchedule(scheduleModel); - }, [scheduleModel, setNewSchedule]); + const { schedule: scheduleLocalState } = useContext(ScheduleLogicContext); function isPresent(): boolean { if (scheduleLocalState.isInitialized) { @@ -33,7 +25,7 @@ export function ScheduleComponent(): JSX.Element { } return ( - + <> {!isPresent() && ( @@ -52,96 +44,94 @@ export function ScheduleComponent(): JSX.Element { {isPresent() && (
- - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + +
- - - - -
- - - - - - -
-
- -
- - - - - - -
-
- -
- - - - - - -
-
-
+ + + + +
+ + + + + + +
+
+ +
+ + + + + + +
+
+ +
+ + + + + + +
+
+
)} -
+ ); } diff --git a/src/components/schedule-page/table/schedule/sections/shifts-section/shifts-section.component.tsx b/src/components/schedule-page/table/schedule/sections/shifts-section/shifts-section.component.tsx index fefa363f8..6e60b7c4f 100644 --- a/src/components/schedule-page/table/schedule/sections/shifts-section/shifts-section.component.tsx +++ b/src/components/schedule-page/table/schedule/sections/shifts-section/shifts-section.component.tsx @@ -16,7 +16,7 @@ export interface ShiftsSectionOptions extends Omit(sectionKey); diff --git a/src/components/schedule-page/table/schedule/use-schedule-state.ts b/src/components/schedule-page/table/schedule/use-schedule-state.ts index 2f6438d0e..f4972a347 100644 --- a/src/components/schedule-page/table/schedule/use-schedule-state.ts +++ b/src/components/schedule-page/table/schedule/use-schedule-state.ts @@ -2,20 +2,27 @@ import React, { useCallback, useState } from "react"; import { useDispatch } from "react-redux"; import { ScheduleLogic } from "../../../../logic/schedule-logic/schedule.logic"; import { ScheduleDataModel } from "../../../../common-models/schedule-data.model"; -import { ScheduleComponentState, scheduleInitialState } from "./schedule-state.model"; import { LocalStorageProvider } from "../../../../api/local-storage-provider.model"; -import { ShiftsInfoLogic } from "../../../../logic/schedule-logic/shifts-info.logic"; -import { MetadataLogic } from "../../../../logic/schedule-logic/metadata.logic"; import { FoundationInfoLogic } from "../../../../logic/schedule-logic/foundation-info.logic"; +import { MetadataLogic } from "../../../../logic/schedule-logic/metadata.logic"; +import { ShiftsInfoLogic } from "../../../../logic/schedule-logic/shifts-info.logic"; +import { ScheduleComponentState, scheduleInitialState } from "./schedule-state.model"; // eslint-disable-next-line @typescript-eslint/class-name-casing export interface useScheduleStateReturn { scheduleLogic: ScheduleLogic; - scheduleLocalState: ScheduleComponentState; setNewSchedule: (scheduleModel: ScheduleDataModel) => void; + scheduleLocalState: ScheduleComponentState; +} + +export interface ScheduleContext { + schedule: ScheduleComponentState; + logic?: ScheduleLogic; } -export const ScheduleLogicContext = React.createContext(null); +export const ScheduleLogicContext = React.createContext({ + schedule: scheduleInitialState, +}); export function useScheduleState( initialScheduleModelState: ScheduleDataModel @@ -24,7 +31,6 @@ export function useScheduleState( const [scheduleLocalState, setScheduleLocalState] = useState( scheduleInitialState ); - const scheduleLogic = useState( new ScheduleLogic(dispatchGlobalState, new LocalStorageProvider(), initialScheduleModelState) )[0]; @@ -32,19 +38,25 @@ export function useScheduleState( const setNewSchedule = useCallback( (scheduleModel: ScheduleDataModel): void => { scheduleLogic.update(scheduleModel); - setScheduleLocalState({ - nurseShiftsSection: (scheduleLogic.sections.NurseInfo as ShiftsInfoLogic).sectionData, - babysitterShiftsSection: (scheduleLogic.sections.BabysitterInfo as ShiftsInfoLogic) - .sectionData, - foundationInfoSection: (scheduleLogic.sections.FoundationInfo as FoundationInfoLogic) - .sectionData, - dateSection: (scheduleLogic.sections.Metadata as MetadataLogic).sectionData, - isInitialized: true, - uuid: scheduleModel.schedule_info?.UUID?.toString() || "", - }); + setScheduleLocalState(extractScheduleLocalState(scheduleLogic)); }, [scheduleLogic] ); - return { scheduleLogic, scheduleLocalState, setNewSchedule }; + return { scheduleLogic, setNewSchedule, scheduleLocalState }; +} + +function extractScheduleLocalState(scheduleLogic: ScheduleLogic | null): ScheduleComponentState { + if (!scheduleLogic) { + return scheduleInitialState; + } + return { + nurseShiftsSection: (scheduleLogic.sections.NurseInfo as ShiftsInfoLogic).sectionData, + babysitterShiftsSection: (scheduleLogic.sections.BabysitterInfo as ShiftsInfoLogic).sectionData, + foundationInfoSection: (scheduleLogic.sections.FoundationInfo as FoundationInfoLogic) + .sectionData, + dateSection: (scheduleLogic.sections.Metadata as MetadataLogic).sectionData, + isInitialized: true, + uuid: scheduleLogic.uuid, + }; } diff --git a/src/components/schedule-page/view-only-page/view-only-toolbar.tsx b/src/components/schedule-page/view-only-page/view-only-toolbar.tsx new file mode 100644 index 000000000..67703784c --- /dev/null +++ b/src/components/schedule-page/view-only-page/view-only-toolbar.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import { ImportButtonsComponent } from "../import-buttons/import-buttons.component"; +import { Link } from "react-router-dom"; +import { Button } from "../../common-components"; + +interface ViewOnlyToolbarOptions { + openEdit: () => void; +} +export function ViewOnlyToolbar({ openEdit }: ViewOnlyToolbarOptions): JSX.Element { + return ( +
+
+ + + + +
+ ); +} diff --git a/src/components/schedule-page/view-only-page/view-only.page.tsx b/src/components/schedule-page/view-only-page/view-only.page.tsx new file mode 100644 index 000000000..fae419696 --- /dev/null +++ b/src/components/schedule-page/view-only-page/view-only.page.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import { ScheduleComponent } from "../table/schedule/schedule.component"; +import { ViewOnlyToolbar } from "./view-only-toolbar"; + +interface ScheduleViewOnlyPageOptions { + openEdit: () => void; +} + +export function ScheduleViewOnlyPage(props: ScheduleViewOnlyPageOptions): JSX.Element { + return ( + <> + +
+ +
+ + ); +} diff --git a/src/components/timetable/timetable-row.component.tsx b/src/components/timetable/timetable-row.component.tsx index d114d44ec..3049a6fc1 100644 --- a/src/components/timetable/timetable-row.component.tsx +++ b/src/components/timetable/timetable-row.component.tsx @@ -12,7 +12,7 @@ export interface TimeTableRowOptions { } export function TimeTableRowF({ dataRow, uuid }: TimeTableRowOptions): JSX.Element { - const scheduleLogic = useContext(ScheduleLogicContext); + const { logic: scheduleLogic } = useContext(ScheduleLogicContext); function getVerboseDates(): [VerboseDate[], number] { if ( diff --git a/src/components/timetable/timetable-section.component.tsx b/src/components/timetable/timetable-section.component.tsx index 4fbd5af7c..08889fcb5 100644 --- a/src/components/timetable/timetable-section.component.tsx +++ b/src/components/timetable/timetable-section.component.tsx @@ -1,21 +1,10 @@ -import React, { useEffect } from "react"; -import { useSelector } from "react-redux"; -import { ApplicationStateModel } from "../../state/models/application-state.model"; -import { - ScheduleLogicContext, - useScheduleState, -} from "../schedule-page/table/schedule/use-schedule-state"; +import React, { useContext } from "react"; +import { ScheduleLogicContext } from "../schedule-page/table/schedule/use-schedule-state"; import { DataRow } from "../../logic/schedule-logic/data-row"; import { TimeTableRow } from "./timetable-row.component"; export function TimeTableSection(): JSX.Element { - const scheduleModel = useSelector((state: ApplicationStateModel) => state.scheduleData.present); - - const { scheduleLogic, scheduleLocalState, setNewSchedule } = useScheduleState(scheduleModel); - - useEffect(() => { - setNewSchedule(scheduleModel); - }, [scheduleModel, setNewSchedule]); + const { schedule: scheduleLocalState } = useContext(ScheduleLogicContext); function getDataRow(): DataRow { if (scheduleLocalState.isInitialized) { @@ -37,9 +26,7 @@ export function TimeTableSection(): JSX.Element { - - - +
diff --git a/src/helpers/verbose-date.helper.ts b/src/helpers/verbose-date.helper.ts index 83b8be978..8d0854088 100644 --- a/src/helpers/verbose-date.helper.ts +++ b/src/helpers/verbose-date.helper.ts @@ -9,7 +9,9 @@ export class VerboseDateHelper { return false; } return ( - !date.isPublicHoliday && !(date.dayOfWeek === WeekDay.SA || date.dayOfWeek === WeekDay.SU) + !date.isPublicHoliday && + date.dayOfWeek !== WeekDay.SU && + !(date.dayOfWeek === WeekDay.SA || date.dayOfWeek === WeekDay.SU) ); } diff --git a/src/logic/schedule-logic/month-info.logic.ts b/src/logic/schedule-logic/month-info.logic.ts index 6eea372a8..ba046ddca 100644 --- a/src/logic/schedule-logic/month-info.logic.ts +++ b/src/logic/schedule-logic/month-info.logic.ts @@ -9,6 +9,10 @@ export class MonthInfoLogic { private monthDates: number[]; private publicHolidaysLogic: PublicHolidaysLogic; + public get currentDate(): [number, number] { + return [new Date().getMonth(), new Date().getFullYear()]; + } + public get dates(): number[] { return this._verboseDates.map((d) => d.date); } diff --git a/src/logic/schedule-logic/schedule.logic.ts b/src/logic/schedule-logic/schedule.logic.ts index 5dc22ea95..399b6af9f 100644 --- a/src/logic/schedule-logic/schedule.logic.ts +++ b/src/logic/schedule-logic/schedule.logic.ts @@ -1,7 +1,5 @@ -import { Dispatch } from "redux"; import { ShiftHelper } from "../../helpers/shifts.helper"; import { StringHelper } from "../../helpers/string.helper"; -import { ActionModel } from "../../state/models/action.model"; import { WorkerType } from "../../common-models/worker-info.model"; import { ScheduleDataModel } from "../../common-models/schedule-data.model"; import { Schedule, ScheduleProvider, Sections } from "../providers/schedule-provider.model"; @@ -11,23 +9,30 @@ import { ExtraWorkersLogic } from "./extra-workers.logic"; import { MetadataLogic } from "./metadata.logic"; import { ShiftsInfoLogic } from "./shifts-info.logic"; import { ChildrenSectionKey, ExtraWorkersSectionKey } from "../section.model"; -import { PersistanceStoreProvider } from "../../api/persistance-store.model"; -import { ScheduleDataActionType } from "../../state/reducers/schedule-data.reducer"; +import { PersistanceStoreProvider, RevisionFilter } from "../../api/persistance-store.model"; +import { + ScheduleActionModel, + ScheduleDataActionType, +} from "../../state/reducers/schedule-data.reducer"; import { FoundationInfoLogic } from "./foundation-info.logic"; import { FoundationInfoOptions } from "../providers/foundation-info-provider.model"; +import { ThunkDispatch } from "redux-thunk"; +import { ApplicationStateModel } from "../../state/models/application-state.model"; export class ScheduleLogic implements ScheduleProvider { schedule!: Schedule; sections!: Sections; - + uuid!: string; constructor( - private dispatchScheduleUpdate: Dispatch>, + private dispatchScheduleUpdate: ThunkDispatch, private storeProvider: PersistanceStoreProvider, scheduleModel: ScheduleDataModel ) { this.update(scheduleModel); + this.tryGetCurrentMonthSchedule(); } public update(schedule: ScheduleDataModel): void { + this.uuid = schedule.schedule_info.UUID ?? ""; this.sections = this.createSections(schedule); this.schedule = new Schedule(this); } @@ -53,7 +58,6 @@ export class ScheduleLogic implements ScheduleProvider { scheduleModel.month_info?.dates, scheduleInfo.daysFromPreviousMonthExists ); - const logics: FoundationInfoOptions = { BabysitterInfo: new ShiftsInfoLogic(babysitterShifts, WorkerType.OTHER, metadata), NurseInfo: new ShiftsInfoLogic(nurseShifts, WorkerType.NURSE, metadata), @@ -65,6 +69,18 @@ export class ScheduleLogic implements ScheduleProvider { return { ...logics, FoundationInfo: foundationLogic, Metadata: metadata }; } + protected tryGetCurrentMonthSchedule(): void { + const [month, year] = this.sections.Metadata.monthLogic.currentDate; + const filter: RevisionFilter = { revisionType: "actual", validityPeriod: { month, year } }; + this.dispatchScheduleUpdate(this.storeProvider.getScheduleRevision(filter)); + } + + public updateActualRevision(): void { + this.dispatchScheduleUpdate( + this.storeProvider.saveScheduleRevision("actual", this.schedule.getDataModel()) + ); + } + public changeShiftFrozenState(rowind: number, shiftIndex: number): void { if (!this.sections.Metadata) return; this.sections.Metadata.changeShiftFrozenState(rowind, shiftIndex); From a86462e307513e384d7b0e398f5d5885d691c525 Mon Sep 17 00:00:00 2001 From: Bohdan Forostianyi Date: Sat, 19 Dec 2020 15:29:21 +0100 Subject: [PATCH 2/3] Fixes compilation issues and problem with recursion when trying to get schedule from current month --- .../add-worker-modal/add-worker-modal.tsx | 147 ------------------ src/components/common-components/index.ts | 1 - .../namestable/worker-info.component.tsx | 2 +- .../edit-page/edit-page-toolbar.component.tsx | 6 +- .../edit-page/schedule-edit.page.tsx | 25 ++- .../schedule-page/schedule-page.component.tsx | 21 +-- .../schedule-parts/base-row.component.tsx | 2 +- .../table/schedule/schedule.component.tsx | 18 ++- .../shifts-section.component.tsx | 63 +------- .../table/schedule/use-schedule-state.ts | 13 +- .../schedule-page/table/table.component.tsx | 10 -- .../view-only-page/view-only.page.tsx | 23 ++- .../summarytable-section.component.tsx | 35 ++--- .../timetable/timetable-row.component.tsx | 2 +- .../timetable/timetable-section.component.tsx | 11 +- .../timetable/timetable.component.tsx | 8 +- src/helpers/verbose-date.helper.ts | 4 +- src/logic/schedule-logic/schedule.logic.ts | 3 +- 18 files changed, 95 insertions(+), 299 deletions(-) delete mode 100644 src/components/common-components/add-worker-modal/add-worker-modal.tsx delete mode 100644 src/components/schedule-page/table/table.component.tsx diff --git a/src/components/common-components/add-worker-modal/add-worker-modal.tsx b/src/components/common-components/add-worker-modal/add-worker-modal.tsx deleted file mode 100644 index 7b8f10f25..000000000 --- a/src/components/common-components/add-worker-modal/add-worker-modal.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { Modal, TextField } from "@material-ui/core"; -import { - WorkerInfoModel, - WorkerType, - WorkerTypeHelper, -} from "../../../common-models/worker-info.model"; -import { Button } from "../button-component/button.component"; - -const initialState = { - name: "", - nameError: false, - time: "", - timeError: false, - actionName: "Dodaj nowego pracownika do sekcji", - isNewWorker: false, -}; - -interface ParseTimeModel { - isTimeFormatValid: boolean; - parsedTime?: number; -} - -interface AddWorkerModalOptions { - isOpened: boolean; - setIsOpened: (status: boolean) => void; - submit: (workerInfo: WorkerInfoModel) => void; - workerType: WorkerType; - workerInfo?: WorkerInfoModel; -} -const NAME_MIN_LENGTH = 5; - -export function AddWorkerModal({ - isOpened, - setIsOpened, - submit, - workerType, - workerInfo, -}: AddWorkerModalOptions): JSX.Element { - const [{ name, nameError, time, timeError, actionName, isNewWorker }, setState] = useState( - initialState - ); - - useEffect(() => { - const { name = "", time = 0 } = workerInfo || {}; - const isNewWorker = name.length === 0; - const actionName = isNewWorker - ? `Dodaj nowego pracownika do sekcji ${WorkerTypeHelper.translate(workerType, true)}` - : `Edytuj pracownika: ${name}`; - setState((prev) => ({ - ...prev, - name, - time: time + "", - actionName, - isNewWorker, - })); - }, [workerInfo, workerType]); - - function clearState(): void { - setState({ ...initialState }); - } - - function onChange(e: React.ChangeEvent): void { - const { name: controlName, value } = e.target; - setState((prevState) => ({ ...prevState, [controlName]: value })); - } - - function parseTimeIfPossible(time: string): ParseTimeModel { - if (new RegExp("^([1-9]/[1-9])$").test(time)) { - const timerArray = time.split("/").map((t) => parseInt(t)); - if (timerArray[0] <= timerArray[1]) { - return { isTimeFormatValid: true, parsedTime: Number(timerArray[0] / timerArray[1]) }; - } - } - if (new RegExp("^([0].[l0-9])|(1.0)|(1)$").test(time)) { - return { isTimeFormatValid: true, parsedTime: Number(time) }; - } - - return { isTimeFormatValid: false }; - } - - function validateName(name: string): boolean { - return name.length >= NAME_MIN_LENGTH; - } - - function handleSubmit(): void { - const { isTimeFormatValid, parsedTime } = parseTimeIfPossible(time); - const isNameValid = validateName(name); - - if (isTimeFormatValid) { - setState((prevState) => ({ ...prevState, timeError: false })); - if (isNameValid) { - submit({ name, time: parsedTime ?? 0 }); - handleClose(); - } else { - setState((prevState) => ({ ...prevState, nameError: true })); - } - } else { - setState((prevState) => ({ ...prevState, timeError: true })); - } - } - - function handleClose(): void { - clearState(); - setIsOpened(false); - } - - const body = ( -
- -
- - - - -
- ); - - return ( - - {body} - - ); -} diff --git a/src/components/common-components/index.ts b/src/components/common-components/index.ts index b778b157c..377437dae 100644 --- a/src/components/common-components/index.ts +++ b/src/components/common-components/index.ts @@ -1,5 +1,4 @@ export * from "./header/header.component"; -export * from "./add-worker-modal/add-worker-modal"; export * from "./autocomplete/autocomplete.component"; export * from "./button-component/button.component"; export * from "./month-switch/month-switch.component"; diff --git a/src/components/namestable/worker-info.component.tsx b/src/components/namestable/worker-info.component.tsx index b3c73e5ae..35699958d 100644 --- a/src/components/namestable/worker-info.component.tsx +++ b/src/components/namestable/worker-info.component.tsx @@ -1,3 +1,4 @@ +import classNames from "classnames/bind"; import React from "react"; import { WorkerInfoModel, @@ -5,7 +6,6 @@ import { WorkerTypeHelper, } from "../../common-models/worker-info.model"; import { StringHelper } from "../../helpers/string.helper"; -import classNames from "classnames/bind"; export function WorkerInfoComponent(info: WorkerInfoModel): JSX.Element { return ( diff --git a/src/components/schedule-page/edit-page/edit-page-toolbar.component.tsx b/src/components/schedule-page/edit-page/edit-page-toolbar.component.tsx index 614096ad9..24536c27b 100644 --- a/src/components/schedule-page/edit-page/edit-page-toolbar.component.tsx +++ b/src/components/schedule-page/edit-page/edit-page-toolbar.component.tsx @@ -17,7 +17,7 @@ interface EditPageToolbarOptions { } export function EditPageToolbar({ closeEdit }: EditPageToolbarOptions): JSX.Element { - const { logic: scheduleLogic } = useContext(ScheduleLogicContext); + const scheduleLogic = useContext(ScheduleLogicContext); const dispatcher = useDispatch(); async function updateScheduleErrors(): Promise { const schedule = scheduleLogic?.schedule.getDataModel(); @@ -77,7 +77,9 @@ export function EditPageToolbar({ closeEdit }: EditPageToolbarOptions): JSX.Elem size="small" className="submit-button" variant="outlined" - onClick={(): void => scheduleLogic && scheduleLogic.updateActualRevision()} + onClick={(): void => { + scheduleLogic && scheduleLogic.updateActualRevision(); + }} > Zapisz diff --git a/src/components/schedule-page/edit-page/schedule-edit.page.tsx b/src/components/schedule-page/edit-page/schedule-edit.page.tsx index 1a16e39e3..98d28ebd2 100644 --- a/src/components/schedule-page/edit-page/schedule-edit.page.tsx +++ b/src/components/schedule-page/edit-page/schedule-edit.page.tsx @@ -1,5 +1,8 @@ -import React from "react"; +import React, { useEffect } from "react"; +import { useSelector } from "react-redux"; +import { ApplicationStateModel } from "../../../state/models/application-state.model"; import { ScheduleComponent } from "../table/schedule/schedule.component"; +import { ScheduleLogicContext, useScheduleState } from "../table/schedule/use-schedule-state"; import { EditPageToolbar } from "./edit-page-toolbar.component"; interface ScheduleEditPageOptions { @@ -7,12 +10,20 @@ interface ScheduleEditPageOptions { } export function ScheduleEditPage(options: ScheduleEditPageOptions): JSX.Element { + const scheduleModel = useSelector((state: ApplicationStateModel) => state.scheduleData.present); + const { scheduleLogic, setNewSchedule, scheduleLocalState } = useScheduleState(scheduleModel); + useEffect(() => { + setNewSchedule(scheduleModel); + }, [scheduleModel, setNewSchedule]); + return ( -
- -
- -
-
+ <> + + +
+ +
+
+ ); } diff --git a/src/components/schedule-page/schedule-page.component.tsx b/src/components/schedule-page/schedule-page.component.tsx index 82e07baf4..30c897cea 100644 --- a/src/components/schedule-page/schedule-page.component.tsx +++ b/src/components/schedule-page/schedule-page.component.tsx @@ -1,9 +1,6 @@ -import React, { useEffect } from "react"; -import { useSelector } from "react-redux"; +import React from "react"; import { Route, Switch } from "react-router-dom"; -import { ApplicationStateModel } from "../../state/models/application-state.model"; import { ScheduleEditPage } from "./edit-page/schedule-edit.page"; -import { ScheduleLogicContext, useScheduleState } from "./table/schedule/use-schedule-state"; import { ScheduleViewOnlyPage } from "./view-only-page/view-only.page"; interface SchedulePageOptions { @@ -11,12 +8,6 @@ interface SchedulePageOptions { } export function SchedulePage(props: SchedulePageOptions): JSX.Element { - const scheduleModel = useSelector((state: ApplicationStateModel) => state.scheduleData.present); - const { scheduleLogic, setNewSchedule, scheduleLocalState } = useScheduleState(scheduleModel); - useEffect(() => { - setNewSchedule(scheduleModel); - }, [scheduleModel, setNewSchedule]); - function ViewOnly(): JSX.Element { return ( <> @@ -34,18 +25,14 @@ export function SchedulePage(props: SchedulePageOptions): JSX.Element { } return ( - + <>
-
+ ) + ); } diff --git a/src/components/schedule-page/table/schedule/schedule-parts/base-row.component.tsx b/src/components/schedule-page/table/schedule/schedule-parts/base-row.component.tsx index 61b68b9a5..8cfc000f0 100644 --- a/src/components/schedule-page/table/schedule/schedule-parts/base-row.component.tsx +++ b/src/components/schedule-page/table/schedule/schedule-parts/base-row.component.tsx @@ -41,7 +41,7 @@ export function BaseRowComponentF({ selection = [], isEditable = true, }: BaseRowOptions): JSX.Element { - const { logic: scheduleLogic } = useContext(ScheduleLogicContext); + const scheduleLogic = useContext(ScheduleLogicContext); const verboseDates = scheduleLogic?.sections.Metadata?.verboseDates; const currMonthNumber = scheduleLogic?.sections.Metadata.monthNumber; const numberOfDays = verboseDates?.length; diff --git a/src/components/schedule-page/table/schedule/schedule.component.tsx b/src/components/schedule-page/table/schedule/schedule.component.tsx index 44a73328e..37e6cf0ad 100644 --- a/src/components/schedule-page/table/schedule/schedule.component.tsx +++ b/src/components/schedule-page/table/schedule/schedule.component.tsx @@ -8,10 +8,15 @@ import { NameTableComponent } from "../../../namestable/nametable.component"; import { SummaryTableComponent } from "../../../summerytable/summarytable.component"; import { TableMiddleLine } from "../../table-middleline"; import { OvertimeHeaderComponent } from "../../../overtime-header-table/overtime-header.component"; +import { ScheduleComponentState } from "./schedule-state.model"; -export function ScheduleComponent(): JSX.Element { - const { schedule: scheduleLocalState } = useContext(ScheduleLogicContext); - +interface ScheduleComponentOptions { + schedule: ScheduleComponentState; +} +export function ScheduleComponent({ + schedule: scheduleLocalState, +}: ScheduleComponentOptions): JSX.Element { + const scheduleLogic = useContext(ScheduleLogicContext); function isPresent(): boolean { if (scheduleLocalState.isInitialized) { const a = scheduleLocalState.dateSection?.length; @@ -21,6 +26,7 @@ export function ScheduleComponent(): JSX.Element { return true; } } + scheduleLogic?.tryGetCurrentMonthSchedule(); return false; } @@ -33,7 +39,7 @@ export function ScheduleComponent(): JSX.Element {
- +
@@ -47,7 +53,7 @@ export function ScheduleComponent(): JSX.Element { - + @@ -64,7 +70,7 @@ export function ScheduleComponent(): JSX.Element { - + { workerType: WorkerType; @@ -16,56 +11,8 @@ export interface ShiftsSectionOptions extends Omit(sectionKey); - const [isOpened, setIsOpened] = useState(false); - const [workerInfo, setWorkerInfo] = useState({ name: "", time: 0 }); - - const addOrUpdateWorker = useCallback( - (newRow: DataRow, workerTime: number): void => { - if (sectionKey) scheduleLogic?.addWorker(sectionKey, newRow, workerTime); - }, - [scheduleLogic, sectionKey] - ); - - const submit = useCallback( - ({ name, time }: WorkerInfoModel): void => { - if (!name || !time) return; - let dataRow = data.find((row) => row.rowKey === name); - if (!dataRow) { - dataRow = new DataRow(name, new Array(data[0].length - 1).fill(ShiftCode.W)); - } - addOrUpdateWorker(dataRow, time); - }, - [data, addOrUpdateWorker] - ); - - const modal = useMemo( - () => ( - - ), - [workerType, workerInfo, isOpened, submit] - ); - - const openWorkerModal = useCallback( - (workerName?: string): void => { - let workerInfo = { name: "", time: 0 }; - if (workerName && sectionInfoProvider) { - workerInfo = { name: workerName, time: sectionInfoProvider.workerWorkTime(workerName) }; - } - setWorkerInfo(workerInfo); - setIsOpened(true); - }, - [setWorkerInfo, setIsOpened, sectionInfoProvider] - ); return ( <> @@ -77,10 +24,8 @@ export function ShiftsSectionComponent(options: ShiftsSectionOptions): JSX.Eleme sectionKey={sectionKey} cellComponent={ShiftCellComponent} rowComponent={ShiftRowComponent} - onRowKeyClicked={(rowIndex): void => openWorkerModal(data[rowIndex].rowKey)} />
- {modal} ); } diff --git a/src/components/schedule-page/table/schedule/use-schedule-state.ts b/src/components/schedule-page/table/schedule/use-schedule-state.ts index f4972a347..f901982f4 100644 --- a/src/components/schedule-page/table/schedule/use-schedule-state.ts +++ b/src/components/schedule-page/table/schedule/use-schedule-state.ts @@ -1,10 +1,10 @@ import React, { useCallback, useState } from "react"; import { useDispatch } from "react-redux"; -import { ScheduleLogic } from "../../../../logic/schedule-logic/schedule.logic"; -import { ScheduleDataModel } from "../../../../common-models/schedule-data.model"; import { LocalStorageProvider } from "../../../../api/local-storage-provider.model"; +import { ScheduleDataModel } from "../../../../common-models/schedule-data.model"; import { FoundationInfoLogic } from "../../../../logic/schedule-logic/foundation-info.logic"; import { MetadataLogic } from "../../../../logic/schedule-logic/metadata.logic"; +import { ScheduleLogic } from "../../../../logic/schedule-logic/schedule.logic"; import { ShiftsInfoLogic } from "../../../../logic/schedule-logic/shifts-info.logic"; import { ScheduleComponentState, scheduleInitialState } from "./schedule-state.model"; @@ -15,14 +15,7 @@ export interface useScheduleStateReturn { scheduleLocalState: ScheduleComponentState; } -export interface ScheduleContext { - schedule: ScheduleComponentState; - logic?: ScheduleLogic; -} - -export const ScheduleLogicContext = React.createContext({ - schedule: scheduleInitialState, -}); +export const ScheduleLogicContext = React.createContext(null); export function useScheduleState( initialScheduleModelState: ScheduleDataModel diff --git a/src/components/schedule-page/table/table.component.tsx b/src/components/schedule-page/table/table.component.tsx deleted file mode 100644 index 3188be8e3..000000000 --- a/src/components/schedule-page/table/table.component.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; -import { ScheduleComponent } from "./schedule/schedule.component"; - -export function TableComponent(): JSX.Element { - return ( -
- -
- ); -} diff --git a/src/components/schedule-page/view-only-page/view-only.page.tsx b/src/components/schedule-page/view-only-page/view-only.page.tsx index fae419696..619b93613 100644 --- a/src/components/schedule-page/view-only-page/view-only.page.tsx +++ b/src/components/schedule-page/view-only-page/view-only.page.tsx @@ -1,5 +1,8 @@ -import React from "react"; +import React, { useEffect } from "react"; +import { useSelector } from "react-redux"; +import { ApplicationStateModel } from "../../../state/models/application-state.model"; import { ScheduleComponent } from "../table/schedule/schedule.component"; +import { ScheduleLogicContext, useScheduleState } from "../table/schedule/use-schedule-state"; import { ViewOnlyToolbar } from "./view-only-toolbar"; interface ScheduleViewOnlyPageOptions { @@ -7,12 +10,22 @@ interface ScheduleViewOnlyPageOptions { } export function ScheduleViewOnlyPage(props: ScheduleViewOnlyPageOptions): JSX.Element { + const scheduleModel = useSelector((state: ApplicationStateModel) => { + return state.scheduleData.present; + }); + const { scheduleLogic, setNewSchedule, scheduleLocalState } = useScheduleState(scheduleModel); + useEffect(() => { + setNewSchedule(scheduleModel); + }, [scheduleModel, setNewSchedule]); + return ( <> - -
- -
+ + +
+ +
+
); } diff --git a/src/components/summerytable/summarytable-section.component.tsx b/src/components/summerytable/summarytable-section.component.tsx index 4fd91077e..586cb9e4a 100644 --- a/src/components/summerytable/summarytable-section.component.tsx +++ b/src/components/summerytable/summarytable-section.component.tsx @@ -1,14 +1,11 @@ import React, { useEffect } from "react"; import { useSelector } from "react-redux"; -import { ApplicationStateModel } from "../../state/models/application-state.model"; -import { - ScheduleLogicContext, - useScheduleState, -} from "../schedule-page/table/schedule/use-schedule-state"; -import { SummaryTableRow } from "./summarytable-row.component"; -import { DataRow } from "../../logic/schedule-logic/data-row"; import { WorkerType } from "../../common-models/worker-info.model"; +import { DataRow } from "../../logic/schedule-logic/data-row"; import { ShiftsInfoLogic } from "../../logic/schedule-logic/shifts-info.logic"; +import { ApplicationStateModel } from "../../state/models/application-state.model"; +import { useScheduleState } from "../schedule-page/table/schedule/use-schedule-state"; +import { SummaryTableRow } from "./summarytable-row.component"; export interface SummaryTableSectionOptions { dataRows: DataRow[]; @@ -30,22 +27,20 @@ export function SummaryTableSection({ }, [scheduleModel, setNewSchedule]); return ( - + <> - - {dataRows.map((dataRow) => { - return ( - - ); - })} - + {dataRows.map((dataRow) => { + return ( + + ); + })}
-
+ ); } diff --git a/src/components/timetable/timetable-row.component.tsx b/src/components/timetable/timetable-row.component.tsx index 3049a6fc1..d114d44ec 100644 --- a/src/components/timetable/timetable-row.component.tsx +++ b/src/components/timetable/timetable-row.component.tsx @@ -12,7 +12,7 @@ export interface TimeTableRowOptions { } export function TimeTableRowF({ dataRow, uuid }: TimeTableRowOptions): JSX.Element { - const { logic: scheduleLogic } = useContext(ScheduleLogicContext); + const scheduleLogic = useContext(ScheduleLogicContext); function getVerboseDates(): [VerboseDate[], number] { if ( diff --git a/src/components/timetable/timetable-section.component.tsx b/src/components/timetable/timetable-section.component.tsx index 08889fcb5..453b03613 100644 --- a/src/components/timetable/timetable-section.component.tsx +++ b/src/components/timetable/timetable-section.component.tsx @@ -1,11 +1,12 @@ -import React, { useContext } from "react"; -import { ScheduleLogicContext } from "../schedule-page/table/schedule/use-schedule-state"; +import React from "react"; import { DataRow } from "../../logic/schedule-logic/data-row"; +import { ScheduleComponentState } from "../schedule-page/table/schedule/schedule-state.model"; import { TimeTableRow } from "./timetable-row.component"; -export function TimeTableSection(): JSX.Element { - const { schedule: scheduleLocalState } = useContext(ScheduleLogicContext); - +export interface TimeTableSectionOptions { + scheduleLocalState: ScheduleComponentState; +} +export function TimeTableSection({ scheduleLocalState }: TimeTableSectionOptions): JSX.Element { function getDataRow(): DataRow { if (scheduleLocalState.isInitialized) { const d = scheduleLocalState.dateSection?.values().next().value as DataRow; diff --git a/src/components/timetable/timetable.component.tsx b/src/components/timetable/timetable.component.tsx index 45a5cacd3..2b8f0ed54 100644 --- a/src/components/timetable/timetable.component.tsx +++ b/src/components/timetable/timetable.component.tsx @@ -1,10 +1,14 @@ import React from "react"; +import { ScheduleComponentState } from "../schedule-page/table/schedule/schedule-state.model"; import { TimeTableSection } from "./timetable-section.component"; +export interface TimeTableComponentOptions { + scheduleLocalState: ScheduleComponentState; +} -export function TimeTableComponent(): JSX.Element { +export function TimeTableComponent(options: TimeTableComponentOptions): JSX.Element { return (
- +
); } diff --git a/src/helpers/verbose-date.helper.ts b/src/helpers/verbose-date.helper.ts index 8d0854088..83b8be978 100644 --- a/src/helpers/verbose-date.helper.ts +++ b/src/helpers/verbose-date.helper.ts @@ -9,9 +9,7 @@ export class VerboseDateHelper { return false; } return ( - !date.isPublicHoliday && - date.dayOfWeek !== WeekDay.SU && - !(date.dayOfWeek === WeekDay.SA || date.dayOfWeek === WeekDay.SU) + !date.isPublicHoliday && !(date.dayOfWeek === WeekDay.SA || date.dayOfWeek === WeekDay.SU) ); } diff --git a/src/logic/schedule-logic/schedule.logic.ts b/src/logic/schedule-logic/schedule.logic.ts index 399b6af9f..c22e9d264 100644 --- a/src/logic/schedule-logic/schedule.logic.ts +++ b/src/logic/schedule-logic/schedule.logic.ts @@ -28,7 +28,6 @@ export class ScheduleLogic implements ScheduleProvider { scheduleModel: ScheduleDataModel ) { this.update(scheduleModel); - this.tryGetCurrentMonthSchedule(); } public update(schedule: ScheduleDataModel): void { @@ -69,7 +68,7 @@ export class ScheduleLogic implements ScheduleProvider { return { ...logics, FoundationInfo: foundationLogic, Metadata: metadata }; } - protected tryGetCurrentMonthSchedule(): void { + public tryGetCurrentMonthSchedule(): void { const [month, year] = this.sections.Metadata.monthLogic.currentDate; const filter: RevisionFilter = { revisionType: "actual", validityPeriod: { month, year } }; this.dispatchScheduleUpdate(this.storeProvider.getScheduleRevision(filter)); From e4500c59b179e98264b25ac5f5ff3c1085377e09 Mon Sep 17 00:00:00 2001 From: Bohdan Forostianyi Date: Sun, 20 Dec 2020 00:42:37 +0100 Subject: [PATCH 3/3] Removes unused abstract methods --- src/api/local-storage-provider.model.ts | 20 ++------------------ src/api/persistance-store.model.ts | 7 +------ 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/api/local-storage-provider.model.ts b/src/api/local-storage-provider.model.ts index ac6c0edbc..300873320 100644 --- a/src/api/local-storage-provider.model.ts +++ b/src/api/local-storage-provider.model.ts @@ -1,16 +1,13 @@ +import PouchDB from "pouchdb-browser"; import { ScheduleDataModel } from "../common-models/schedule-data.model"; -import { ShiftModel } from "../common-models/shift-info.model"; -import { WorkerInfoModel, WorkersInfoModel } from "../common-models/worker-info.model"; import { ScheduleDataActionType } from "../state/reducers/schedule-data.reducer"; import { PersistanceStoreProvider, RevisionFilter, + RevisionType, ScheduleRevision, ThunkFunction, - ScheduleKey, - RevisionType, } from "./persistance-store.model"; -import PouchDB from "pouchdb-browser"; /*eslint-disable @typescript-eslint/camelcase */ export class LocalStorageProvider extends PersistanceStoreProvider { @@ -65,17 +62,4 @@ export class LocalStorageProvider extends PersistanceStoreProvider { }); }; } - addNewWorker(worker: WorkerInfoModel): ThunkFunction { - throw Error("Not implemented method"); - } - getWorkers(period: ScheduleKey): ThunkFunction { - throw Error("Not implemented method"); - } - - addNewShift(shift: ShiftModel): ThunkFunction { - throw Error("Not implemented method"); - } - getShifts(period: ScheduleKey): ThunkFunction { - throw Error("Not implemented method"); - } } diff --git a/src/api/persistance-store.model.ts b/src/api/persistance-store.model.ts index c05919c5f..3dcc28ff0 100644 --- a/src/api/persistance-store.model.ts +++ b/src/api/persistance-store.model.ts @@ -1,7 +1,6 @@ import { Dispatch } from "react"; import { ScheduleDataModel } from "../common-models/schedule-data.model"; -import { ShiftModel } from "../common-models/shift-info.model"; -import { WorkerInfoModel, WorkersInfoModel } from "../common-models/worker-info.model"; +import { WorkerInfoModel } from "../common-models/worker-info.model"; import { ActionModel } from "../state/models/action.model"; import { ApplicationStateModel } from "../state/models/application-state.model"; @@ -41,10 +40,6 @@ export abstract class PersistanceStoreProvider { schedule: ScheduleDataModel ): ThunkFunction; abstract getScheduleRevision(filter: RevisionFilter): ThunkFunction; - abstract addNewWorker(worker: WorkerInfoModel): ThunkFunction; - abstract getWorkers(period: ScheduleKey): ThunkFunction; - abstract addNewShift(shift: ShiftModel): ThunkFunction; - abstract getShifts(period: ScheduleKey): ThunkFunction; protected getScheduleId(schedule: ScheduleDataModel): string { const { month_number, year } = schedule.schedule_info; return `${month_number}_${year}`;