diff --git a/cypress/integration/e2e/app-error-handling/schedule-errors.spec.ts b/cypress/integration/e2e/app-error-handling/schedule-errors.spec.ts index d3730bb6d..952eb1c40 100644 --- a/cypress/integration/e2e/app-error-handling/schedule-errors.spec.ts +++ b/cypress/integration/e2e/app-error-handling/schedule-errors.spec.ts @@ -4,7 +4,7 @@ import { WorkerType } from "../../../../src/common-models/worker-info.model"; -const addWorker = (workerName: string, position: WorkerType) => { +const addWorker = (workerName: string, position: WorkerType): Cypress.Chainable => { cy.get('[data-cy="btn-management-tab"]').click(); cy.get('[data-cy="management-page-title"]').should("be.visible"); cy.get('[data-cy="btn-add-worker"]').click(); diff --git a/cypress/integration/e2e/management-tab/edit-worker.spec.ts b/cypress/integration/e2e/management-tab/edit-worker.spec.ts index 324073dc7..009c69bfd 100644 --- a/cypress/integration/e2e/management-tab/edit-worker.spec.ts +++ b/cypress/integration/e2e/management-tab/edit-worker.spec.ts @@ -17,7 +17,7 @@ context("Tab management", () => { it("Should be able to set worker name", () => { cy.get('[data-cy="name"]').type(newWorker); - cy.get(`[value=\"${newWorker}\"]`).should("be.visible"); + cy.get(`[value="${newWorker}"]`).should("be.visible"); }); it("Should be able to set worker position", () => { diff --git a/src/app.tsx b/src/app.tsx index d40bd086a..6f3d5a10e 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -127,7 +127,7 @@ function App(): JSX.Element { - + {isElectron() ? <> : } diff --git a/src/assets/styles/styles/custom/_error-list-item.scss b/src/assets/styles/styles/custom/_error-list-item.scss index 8f66db43f..b2463a8b2 100644 --- a/src/assets/styles/styles/custom/_error-list-item.scss +++ b/src/assets/styles/styles/custom/_error-list-item.scss @@ -17,7 +17,7 @@ .red-rectangle { border-radius: 4px; - width: 0.8%; + width: 4.5px; position: absolute; height: 100%; background-color: $error-red; @@ -29,6 +29,7 @@ flex: 1; text-align: center; padding-left: 15px; + padding-right: 25px; position: relative; .error-title-content { @@ -47,6 +48,10 @@ margin: 10px 10px; text-align: justify; font-family: "Roboto"; + strong { + letter-spacing: 1.5px; + font-weight: bolder; + } } .error-btn { diff --git a/src/assets/styles/styles/custom/_tables.scss b/src/assets/styles/styles/custom/_tables.scss index 7d5d21d87..1572b45c2 100644 --- a/src/assets/styles/styles/custom/_tables.scss +++ b/src/assets/styles/styles/custom/_tables.scss @@ -322,6 +322,7 @@ p { font-size: 13px; border-radius: 4px; z-index: 3; + max-width: 500px; } .cell-details-popper { @@ -331,8 +332,6 @@ p { padding: 10px; font-size: 13px; border-radius: 4px; - // position: absolute; - // margin-left: 35px; z-index: 2; box-shadow: 0 1.4px 1.1px rgba(0, 0, 0, 0.034), 0 3.3px 2.7px rgba(0, 0, 0, 0.048), 0 6.2px 5px rgba(0, 0, 0, 0.06), 0 11.1px 8.5px rgba(0, 0, 0, 0.072), @@ -340,7 +339,7 @@ p { overflow: hidden; } -.errorTootlip-item { +.errorTooltip-item { margin: 0px !important; .error-title { flex: 0.5 !important; diff --git a/src/components/common-components/drawer/jira-like-drawer.component.tsx b/src/components/common-components/drawer/jira-like-drawer.component.tsx index 1ab70e8b8..95cae31e8 100644 --- a/src/components/common-components/drawer/jira-like-drawer.component.tsx +++ b/src/components/common-components/drawer/jira-like-drawer.component.tsx @@ -4,13 +4,17 @@ import React from "react"; import DrawerHeader from "./drawer-header.component"; import { Box } from "@material-ui/core"; -import { makeStyles } from "@material-ui/core/styles"; +import { makeStyles, Theme } from "@material-ui/core/styles"; import { useJiraLikeDrawer } from "./jira-like-drawer-context"; import ScssVars from "../../../assets/styles/styles/custom/_variables.module.scss"; -const useStyles = makeStyles({ +export interface StyleProps { + width: number; +} + +const useStyles = makeStyles({ drawer: { - minWidth: 690, + width: ({ width }): number => width, height: `calc(100vh - ${ parseInt(ScssVars.headerHeight.slice(0, -2)) + parseInt(ScssVars.drawerHeaderHeight.slice(0, -2)) + @@ -20,8 +24,8 @@ const useStyles = makeStyles({ }, }); -export default function JiraLikeDrawer(): JSX.Element { - const classes = useStyles(); +export default function JiraLikeDrawer(width): JSX.Element { + const classes = useStyles(width); const { title, open, setOpen, childrenComponent } = useJiraLikeDrawer(); return ( diff --git a/src/components/schedule-page/table/schedule/schedule-parts/error-tooltip-provider.component.tsx b/src/components/schedule-page/table/schedule/schedule-parts/error-tooltip-provider.component.tsx index 236f52769..3e752b700 100644 --- a/src/components/schedule-page/table/schedule/schedule-parts/error-tooltip-provider.component.tsx +++ b/src/components/schedule-page/table/schedule/schedule-parts/error-tooltip-provider.component.tsx @@ -94,7 +94,7 @@ export function ErrorTooltipProvider({ key={`${error.kind}_${index}`} error={ErrorMessageHelper.getErrorMessage(error)} interactable={false} - className="errorTootlip-item" + className="errorTooltip-item" showTitle={showErrorTitle} /> ))} diff --git a/src/components/schedule-page/validation-drawer/error-list.component.tsx b/src/components/schedule-page/validation-drawer/error-list.component.tsx index 079eaf39d..27d375f2a 100644 --- a/src/components/schedule-page/validation-drawer/error-list.component.tsx +++ b/src/components/schedule-page/validation-drawer/error-list.component.tsx @@ -39,7 +39,7 @@ export default function ErrorList({ errors = [] }: Options): JSX.Element { { errorType: ScheduleErrorType.AON, errors: errors.filter((e) => e.type === ScheduleErrorType.DSS), - errorDescription: "Niedozwolona sekwencja zmian", + errorDescription: "Naruszenie wymaganej przerwy", }, { errorType: ScheduleErrorType.AON, diff --git a/src/helpers/error-message.helper.ts b/src/helpers/error-message.helper.ts index 229f70843..b255d460b 100644 --- a/src/helpers/error-message.helper.ts +++ b/src/helpers/error-message.helper.ts @@ -14,8 +14,11 @@ import { ParseErrorCode, ScheduleError, } from "../common-models/schedule-error.model"; +import { SHIFTS as shifts } from "../common-models/shift-info.model"; import { ColorHelper } from "./colors/color.helper"; import { Color, Colors } from "./colors/color.model"; +import { ShiftHelper } from "./shifts.helper"; +import { TranslationHelper } from "./translations.helper"; type Error = ScheduleErrorLevel; @@ -32,12 +35,6 @@ export class ErrorMessageHelper { } public static getErrorMessage(error: ScheduleError): ScheduleErrorMessageModel { - const dayTimeTranslations = { - MORNING: "porannej", - AFTERNOON: "popołudniowej", - NIGHT: "nocnej", - }; - const kind = error.kind; let message: string; let title = "Nie rozpoznano błędu"; @@ -48,18 +45,17 @@ export class ErrorMessageHelper { switch (error.kind) { case AlgorithmErrorCode.AlwaysAtLeastOneNurse: i = 0; - message = `Brak pielęgniarek w dniu ${error.day + 1} na zmianie ${ - error.day_time ? dayTimeTranslations[error.day_time] : "" - }`; - if (error.segments[i][0] !== 1 && error.segments[i][1] !== 24) { - message += ` w godzinach ${error.segments[i][0]}-${error.segments[i][1]}`; + message = `Brak pielęgniarek`; + if (error.segments[i][0] !== 1 || error.segments[i][1] !== 24) { + message += ` w godzinach ${error.segments[i][0]}-${error.segments[i][1]}`; } while (error.segments[i + 1]) { i++; - if (error.segments[i][0] !== 1 && error.segments[i][1] !== 24) { - message += `, ${error.segments[i][0]}-${error.segments[i][1]}`; + if (error.segments[i][0] !== 1 || error.segments[i][1] !== 24) { + message += `, ${error.segments[i][0]}-${error.segments[i][1]}`; } } + message += `.`; type = ScheduleErrorType.AON; title = "date"; if (error.day) { @@ -68,17 +64,17 @@ export class ErrorMessageHelper { break; case AlgorithmErrorCode.WorkerNumberDuringDay: i = 0; - message = `Za mało pracowników w trakcie dnia w dniu ${error.day + 1}`; - if (error.segments && error.segments[i][0] !== 1 && error.segments[i][1] !== 24) { - message += ` w godzinach ${error.segments[i][0]}-${error.segments[i][1]}`; + message = `Za mało pracowników w trakcie dnia`; + if (error.segments && (error.segments[i][0] !== 6 || error.segments[i][1] !== 22)) { + message += ` w godzinach ${error.segments[i][0]}-${error.segments[i][1]}`; while (error.segments[i + 1]) { i++; - if (error.segments[i][0] !== 1 && error.segments[i][1] !== 24) { - message += `, ${error.segments[i][0]}-${error.segments[i][1]}`; + if (error.segments[i][0] !== 6 || error.segments[i][1] !== 22) { + message += `, ${error.segments[i][0]}-${error.segments[i][1]}`; } } } - message += `, potrzeba ${error.required}, jest ${error.actual}`; + message += `: potrzeba ${error.required}, jest ${error.actual}.`; type = ScheduleErrorType.WND; title = "date"; if (error.day) { @@ -87,17 +83,17 @@ export class ErrorMessageHelper { break; case AlgorithmErrorCode.WorkerNumberDuringNight: i = 0; - message = `Za mało pracowników w nocy w dniu ${error.day + 1}`; - if (error.segments && error.segments[i][0] !== 22 && error.segments[i][1] !== 6) { - message += ` w godzinach ${error.segments[i][0]}-${error.segments[i][1]}`; + message = `Za mało pracowników w nocy`; + if (error.segments && (error.segments[i][0] !== 22 || error.segments[i][1] !== 6)) { + message += ` w godzinach ${error.segments[i][0]}-${error.segments[i][1]}`; while (error.segments[i + 1]) { i++; - if (error.segments[i][0] !== 22 && error.segments[i][1] !== 6) { - message += `, ${error.segments[i][0]}-${error.segments[i][1]}`; + if (error.segments[i][0] !== 22 || error.segments[i][1] !== 6) { + message += `, ${error.segments[i][0]}-${error.segments[i][1]}`; } } } - message += `, potrzeba ${error.required}, jest ${error.actual}`; + message += `: potrzeba ${error.required}, jest ${error.actual}.`; type = ScheduleErrorType.WNN; title = "date"; if (error.day) { @@ -105,11 +101,24 @@ export class ErrorMessageHelper { } break; case AlgorithmErrorCode.DissalowedShiftSequence: - message = `Niedozwolona sekwencja zmian dla pracownika ${ + const timeNeeded = ShiftHelper.requiredFreeTimeAfterShift(shifts[error.preceding]); + const [earliestPossible, nextDay] = ShiftHelper.nextLegalShiftStart( + shifts[error.preceding] + ); + let tooEarly = earliestPossible - shifts[error.succeeding].from; + if (tooEarly < 1) tooEarly += 24; + message = `Pracownik ${ error.worker - } w dniu ${error.day + 1}: ${ - error.succeeding - } po ${error.preceding}`; + } potrzebuje ${timeNeeded} godzin przerwy po zmianie ${ + error.preceding + } + (${shifts[error.preceding].from}-${shifts[error.preceding].to}). + Nie może mieć zmiany wcześniej niż o ${earliestPossible}`; + if (nextDay) message += ` następnego dnia`; + message += `. Przypisana zmiana ${error.succeeding} (${ + shifts[error.succeeding].from + }-${shifts[error.succeeding].to}) zaczyna się + o ${tooEarly} ${TranslationHelper.hourAccusativus(tooEarly)} za wcześnie.`; type = ScheduleErrorType.DSS; title = "date"; if (error.day) { @@ -117,26 +126,28 @@ export class ErrorMessageHelper { } break; case AlgorithmErrorCode.LackingLongBreak: - message = `Brak wymaganej długiej przerwy dla pracownika ${ - error.worker - } w tygodniu ${error.week + 1}`; + message = `Brak wymaganej długiej przerwy w tygodniu ${error.week + 1}.`; type = ScheduleErrorType.LLB; title = `${error.worker}`; break; case AlgorithmErrorCode.WorkerUnderTime: - message = `Pracownik ${error.worker} ma ${error.hours} niedogodzin`; + message = `Pracownik ma ${error.hours} niedo${TranslationHelper.hourAccusativus( + error.hours + )}.`; type = ScheduleErrorType.WUH; title = `${error.worker}`; break; case AlgorithmErrorCode.WorkerOvertime: - message = `Pracownik ${error.worker} ma ${error.hours} nadgodzin`; + message = `Pracownik ma ${error.hours} nad${TranslationHelper.hourAccusativus( + error.hours + )}.`; type = ScheduleErrorType.WOH; title = `${error.worker}`; break; case ParseErrorCode.UNKNOWN_VALUE: - message = `Nieznana wartość zmiany: "${error.actual}" dla pracownika ${ - error.worker - } w dniu ${error.day! + 1}. Przyjęto, że zmiana to wolne.`; + message = `Nieznana wartość zmiany: "${error.actual}" w dniu ${ + error.day! + 1 + }. Przyjęto, że zmiana to wolne.`; type = ScheduleErrorType.ILLEGAL_SHIFT_VALUE; title = `${error.worker}`; break; diff --git a/src/helpers/shifts.helper.ts b/src/helpers/shifts.helper.ts index 3b8a5c3d9..4f22ab84a 100644 --- a/src/helpers/shifts.helper.ts +++ b/src/helpers/shifts.helper.ts @@ -48,6 +48,21 @@ export class ShiftHelper { return duration === 0 ? 24 : duration; } + public static requiredFreeTimeAfterShift(shift: Shift): number { + if (this.shiftCodeToWorkTime(shift) < 9) return 11; + if (this.shiftCodeToWorkTime(shift) > 12) return 24; + return 16; + } + + public static nextLegalShiftStart(shift: Shift): [number, boolean] { + const sum = shift.to + this.requiredFreeTimeAfterShift(shift); + if (sum > 24) { + if ((shift.to + this.requiredFreeTimeAfterShift(shift)) % 24 === 0) return [24, true]; + return [(shift.to + this.requiredFreeTimeAfterShift(shift)) % 24, true]; + } + return [sum, false]; + } + public static groupShiftsByWorkerType( shifts: ShiftInfoModel = {}, workerTypes: { [workerName: string]: WorkerType } = {} diff --git a/src/helpers/translations.helper.ts b/src/helpers/translations.helper.ts index 28ac0006a..5e91b9d7d 100644 --- a/src/helpers/translations.helper.ts +++ b/src/helpers/translations.helper.ts @@ -66,4 +66,11 @@ export class TranslationHelper { SU: "nd", }; } + + public static hourAccusativus(key: number): string { + if (key === 1) return "godzinę"; + if (key === 12 || key === 13 || key === 14) return "godzin"; + if ((key - 2) % 10 === 0 || (key - 3) % 10 === 0 || (key - 4) % 10 === 0) return "godziny"; + return "godzin"; + } }