Unverified Commit 05c7393d authored by pom421's avatar pom421 Committed by GitHub
Browse files

Evolutions after ministries meetings (#86)

- la phrase de titre dans la déclaration ETP a été modifiée avec le lien vers la FAQ
- le placeholder des cases ETP ont été modifiées avec "ex: 2,1" pour montrer que le chiffre à rentrer est un réel
- les champs ETP acceptent maintenant des nombres flottants, et plus seulement des entiers
- mise à jour des titres et légendes non claires sur la page des statistiques (Avec réquisition -> Avec n° de réquisition, etc.)
- changement de nom de rôle pour  "Superviseur publique", "Superviseur de plusieurs UMJ IML", Invité
- un utilisateur avec le rôle Ministère a maintenant un menu lui permettant de voir la liste des actes, en plus des statistiques
- pictogramme d'erreur sur la page de déclaration d'acte
- interdire une année antérieure à N-1 dans déclaration d'acte
- Examens et Prélèvements n'étaient plus obligatoires dans la déclaration d'acte. Corrigé.

* feat: rename some statistics titles and legends

* feat(roles): update description of invité

* feat(etp): inputs accept float number now

* feat: use an icon in error for InputError component

* fix: the examinations category was not present in error detection

* feat: add control to not allow an examination date too old

* chore: change version for planned release
parent 2e75b4ba
Pipeline #68567 failed with stages
in 5 minutes and 52 seconds
{
"name": "medle",
"version": "1.2.10",
"version": "1.2.13",
"private": true,
"scripts": {
"dev": "node -r dotenv/config node_modules/next/dist/bin/next dev",
......
import { objToArray } from "../../../utils/object"
test("objToArray without labels should use object's properties", () => {
const obj = {
"Avec réquisition": 13,
"Recueil de preuve sans plainte": 2,
"Sans réquisition": 143,
}
expect(objToArray(obj)).toMatchInlineSnapshot(`
Array [
Object {
"name": "Avec réquisition",
"value": 13,
},
Object {
"name": "Recueil de preuve sans plainte",
"value": 2,
},
Object {
"name": "Sans réquisition",
"value": 143,
},
]
`)
})
test("objToArray with labels should use labels array", () => {
const obj = {
"Avec réquisition": 13,
"Recueil de preuve sans plainte": 2,
"Sans réquisition": 143,
}
expect(objToArray(obj, ["Avec réquisition", "Sans réquisition"])).toMatchInlineSnapshot(`
Array [
Object {
"name": "Avec réquisition",
"value": 13,
},
Object {
"name": "Sans réquisition",
"value": 143,
},
]
`)
})
test("objToArray with invalid labels should use object's properties", () => {
const obj = {
"Avec réquisition": 13,
"Recueil de preuve sans plainte": 2,
"Sans réquisition": 143,
}
expect(
objToArray(obj, [
{ "Avec réquisition": "Avec n° de réquisition" },
{ "Sans réquisition": "Sans n° de réquisition" },
])
).toMatchInlineSnapshot(`
Array [
Object {
"name": "Avec n° de réquisition",
"value": 13,
},
Object {
"name": "Sans n° de réquisition",
"value": 143,
},
]
`)
})
test("objToArray with labels: bad content (object version) must be ignored", () => {
const obj = {
"Avec réquisition": 13,
"Recueil de preuve sans plainte": 2,
"Sans réquisition": 143,
}
expect(
objToArray(obj, [
{ "Avec réquisition": "Avec n° de réquisition" },
{ "autre clé qui n'existe pas": "autre clé qui n'existe pas" },
])
).toMatchInlineSnapshot(`
Array [
Object {
"name": "Avec n° de réquisition",
"value": 13,
},
]
`)
})
test("objToArray with labels: bad content (string version) must be ignored", () => {
const obj = {
"Avec réquisition": 13,
"Recueil de preuve sans plainte": 2,
"Sans réquisition": 143,
}
expect(objToArray(obj, [{ "Avec réquisition": "Avec n° de réquisition" }, "autre clé qui n'existe pas"]))
.toMatchInlineSnapshot(`
Array [
Object {
"name": "Avec n° de réquisition",
"value": 13,
},
]
`)
})
......@@ -132,6 +132,9 @@ const AccordionEmploymentsMonth = ({ monthName, month, year, hospitalId, readOnl
<Label htmlFor="doctors">Médecin</Label>
<Input
name="doctors"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.doctors}
placeholder="Nombre d'ETP"
value={dataMonth["doctors"] || ""}
......@@ -145,6 +148,9 @@ const AccordionEmploymentsMonth = ({ monthName, month, year, hospitalId, readOnl
<Label htmlFor="secretaries">Secrétaire</Label>
<Input
name="secretaries"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.secretaries}
placeholder="Nombre d'ETP"
value={dataMonth["secretaries"] || ""}
......@@ -158,6 +164,9 @@ const AccordionEmploymentsMonth = ({ monthName, month, year, hospitalId, readOnl
<Label htmlFor="nursings">Aide soignant.e</Label>
<Input
name="nursings"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.nursings}
placeholder="Nombre d'ETP"
value={dataMonth["nursings"] || ""}
......@@ -171,6 +180,9 @@ const AccordionEmploymentsMonth = ({ monthName, month, year, hospitalId, readOnl
<Label htmlFor="executives">Cadre de santé</Label>
<Input
name="executives"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.executives}
placeholder="Nombre d'ETP"
value={dataMonth["executives"] || ""}
......@@ -186,6 +198,9 @@ const AccordionEmploymentsMonth = ({ monthName, month, year, hospitalId, readOnl
<Label htmlFor="idesNumber">IDE</Label>
<Input
name="idesNumber"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.idesNumber}
placeholder="Nombre d'ETP"
value={dataMonth["idesNumber"] || ""}
......@@ -199,6 +214,9 @@ const AccordionEmploymentsMonth = ({ monthName, month, year, hospitalId, readOnl
<Label htmlFor="auditoriumAgents">{"Agent d'amphithéâtre"}</Label>
<Input
name="auditoriumAgents"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.auditoriumAgents}
placeholder="Nombre d'ETP"
value={dataMonth["auditoriumAgents"] || ""}
......@@ -212,6 +230,9 @@ const AccordionEmploymentsMonth = ({ monthName, month, year, hospitalId, readOnl
<Label htmlFor="others">Autres</Label>
<Input
name="others"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.others}
placeholder="Nombre d'ETP"
value={dataMonth["others"] || ""}
......
......@@ -3,6 +3,7 @@ import PropTypes from "prop-types"
import { ButtonDropdown, Col, DropdownItem, DropdownMenu, Row } from "reactstrap"
import { Button, DropdownToggle, Title2 } from "./StyledComponents"
import { InputError } from "./InputError"
const normalizeValues = (values) => {
values.forEach((v) => {
......@@ -115,14 +116,16 @@ const ActBlock = ({ title, subTitle, detail, type, values, dispatch, state, inva
<>
{title && (
<Title2 className="mt-5 mb-4" style={colorOptions}>
{title}
{title}&nbsp;
{invalid && <InputError />}
</Title2>
)}
{subTitle && (
<Row className="mt-3">
<Col sm={4} className="mb-1" style={colorOptions}>
{subTitle}
{subTitle}&nbsp;
{invalid && <InputError />}
</Col>
</Row>
)}
......
import React from "react"
import PropTypes from "prop-types"
import ErrorOutlineOutlinedIcon from "@material-ui/icons/ErrorOutlineOutlined"
export const InputError = ({ children }) => {
return (
<>
{children}&nbsp;
<ErrorOutlineOutlinedIcon fontSize="small" />
</>
)
}
InputError.propTypes = {
children: PropTypes.node,
}
......@@ -18,8 +18,7 @@ const Login = ({ authentication, error }) => {
setIsLoading(true)
try {
await authentication({ email: emailRef.current.value, password: passwordRef.current.value })
setIsLoading(false)
} catch (ignore) {
} finally {
setIsLoading(false)
}
}
......
......@@ -3,6 +3,7 @@ import { PropTypes } from "prop-types"
import { Pie, PieChart, Cell, Legend, Tooltip } from "recharts"
import HelpOutlineIcon from "@material-ui/icons/HelpOutline"
import { Title2 } from "./StyledComponents"
import { objToArray } from "../utils/object"
const StatBlock = ({ children }) => {
return (
......@@ -72,16 +73,6 @@ StatBlockNumbers.propTypes = {
secondLabel: PropTypes.string,
}
const objToArray = (obj, labels = []) => {
if (!obj) return []
if (labels?.length) {
return labels.map((curr) => ({ name: curr, value: obj[curr] || 0 }))
} else {
return Object.keys(obj).map((curr) => ({ name: curr, value: obj[curr] || 0 }))
}
}
export const StatBlockPieChart = ({ data, labels = [], hoverTitle, title }) => {
const values = objToArray(data, labels)
......
......@@ -112,6 +112,9 @@ export const Title2 = styled.h2`
letter-spacing: -0.41px;
text-align: center;
font-weight: 400;
display: flex;
align-items: center;
justify-content: center;
`
export const Label = styled.label`
......
......@@ -103,6 +103,9 @@ const hasErrors = (state) => {
if (!state.examinationTypes?.length) {
errors.examinationTypes = "Obligatoire"
}
if (!state.examinations?.length) {
errors.examinations = "Obligatoire"
}
if (!state.periodOfDay) {
errors.periodOfDay = "Obligatoire"
}
......
......@@ -94,6 +94,9 @@ const hasErrors = (state) => {
if (!state.examinationTypes?.length) {
errors.examinationTypes = "Obligatoire"
}
if (!state.examinations?.length) {
errors.examinations = "Obligatoire"
}
if (!state.periodOfDay) {
errors.periodOfDay = "Obligatoire"
}
......
......@@ -46,7 +46,7 @@ const CustodyEdit = ({ dispatch, state, errors }) => {
values={["UMJ", "Commissariat", "Gendarmerie", "Tribunal"]}
mode="toggle"
dispatch={dispatch}
state={state.location || []}
state={state.location || ""}
invalid={!!errors.location}
/>
......@@ -119,6 +119,9 @@ const hasErrors = (state) => {
if (!state.examinationTypes?.length) {
errors.examinationTypes = "Obligatoire"
}
if (!state.examinations?.length) {
errors.examinations = "Obligatoire"
}
if (!state.location) {
errors.location = "Obligatoire"
}
......
......@@ -118,6 +118,9 @@ const hasErrors = (state) => {
if (state.examinationTypes && state.examinationTypes.includes("Levée de corps") && !state.distance) {
errors.distance = "Obligatoire"
}
if (!state.examinations?.length) {
errors.examinations = "Obligatoire"
}
if (!state.periodOfDay) {
errors.periodOfDay = "Obligatoire"
}
......
......@@ -36,7 +36,7 @@ const DrunkEdit = ({ dispatch, state, errors }) => {
values={["UMJ", "Commissariat", "Gendarmerie"]}
mode="toggle"
dispatch={dispatch}
state={state.location || []}
state={state.location || ""}
invalid={!!errors.location}
/>
......@@ -115,7 +115,9 @@ const hasErrors = (state) => {
if (!state.examinationTypes?.length) {
errors.examinationTypes = "Obligatoire"
}
if (!state.examinations?.length) {
errors.examinations = "Obligatoire"
}
if (!state.location) {
errors.location = "Obligatoire"
}
......
......@@ -37,7 +37,7 @@ const RestrainedEdit = ({ dispatch, state, errors }) => {
values={["UMJ", "Locaux douaniers", "Centre de rétention", "Commissariat"]}
mode="toggle"
dispatch={dispatch}
state={state.location || []}
state={state.location || ""}
invalid={!!errors.location}
/>
<ActBlock
......@@ -120,6 +120,9 @@ const hasErrors = (state) => {
if (!state.examinationTypes?.length) {
errors.examinationTypes = "Obligatoire"
}
if (!state.examinations?.length) {
errors.examinations = "Obligatoire"
}
if (!state.location) {
errors.location = "Obligatoire"
}
......
......@@ -36,7 +36,7 @@ const RoadRelatedExaminationEdit = ({ dispatch, state, errors }) => {
values={["UMJ", "Lieu de contrôle", "Commissariat", "Gendarmerie"]}
mode="toggle"
dispatch={dispatch}
state={state.location || []}
state={state.location || ""}
invalid={!!errors.location}
/>
<ActBlock
......@@ -119,6 +119,9 @@ const hasErrors = (state) => {
if (!state.examinationTypes?.length) {
errors.examinationTypes = "Obligatoire"
}
if (!state.examinations?.length) {
errors.examinations = "Obligatoire"
}
if (!state.location) {
errors.location = "Obligatoire"
}
......
......@@ -83,7 +83,7 @@ const VictimEdit = ({ dispatch, state, errors }) => {
]}
mode="toggle"
dispatch={dispatch}
state={state.location || []}
state={state.location || ""}
invalid={!!errors.location}
/>
<ActBlock
......@@ -172,6 +172,12 @@ const hasErrors = (state) => {
if (!state.violenceContexts?.length) {
errors.violenceContexts = "Obligatoire"
}
if (!state.examinations?.length) {
errors.examinations = "Obligatoire"
}
if (!state.location) {
errors.location = "Obligatoire"
}
if (!state.periodOfDay) {
errors.periodOfDay = "Obligatoire"
}
......
......@@ -7,6 +7,7 @@ import moment from "moment"
import AskerSelect from "../../components/AskerSelect"
import { isEmpty, deleteProperty } from "../../utils/misc"
import Layout from "../../components/Layout"
import { InputError } from "../../components/InputError"
import ActBlock from "../../components/ActBlock"
import { Title1, Title2, Label, ValidationButton } from "../../components/StyledComponents"
import { ACT_MANAGEMENT } from "../../utils/roles"
......@@ -71,14 +72,20 @@ const hasErrors = (state) => {
if (!date.isValid()) {
errors = { ...errors, examinationDate: "Format incorrect" }
} else {
const previousYear = now().year() - 1
const limitInPast = moment(`${previousYear}-01-01`)
if (date > now()) {
errors = { ...errors, examinationDate: "La date doit être passée" }
}
if (date < limitInPast) {
errors = { ...errors, examinationDate: `La date est trop ancienne` }
}
}
}
if (!state.askerId && !state.proofWithoutComplaint) {
errors = { ...errors, askerId: "Demandeur manquant ou invalide" }
errors = { ...errors, askerId: <InputError>{"Demandeur manquant ou invalide"}</InputError> }
}
return errors
......@@ -141,6 +148,7 @@ const ActDeclaration = ({ act, currentUser }) => {
logDebug("reducer", state, action)
setErrors(deleteProperty(errors, action.type))
setWarnings(deleteProperty(warnings, action.type))
switch (action.type) {
case "examinationDate": {
......@@ -315,7 +323,9 @@ const ActDeclaration = ({ act, currentUser }) => {
onBlur={onBlurNumberInputs("internalNumber")}
/>
{warnings && warnings.internalNumber && <FormText color="warning">Ce numéro existe déjà</FormText>}
<FormFeedback>{errors && errors.internalNumber}</FormFeedback>
<FormFeedback>
<InputError>{errors && errors.internalNumber}</InputError>
</FormFeedback>
</Col>
<Col sm="6" md="4" className="mt-3 mt-sm-0">
<Label htmlFor="examinationDate" className="mb-0">
......@@ -329,7 +339,9 @@ const ActDeclaration = ({ act, currentUser }) => {
// value={state.examinationDate}
onChange={(e) => dispatch({ type: e.target.id, payload: { val: e.target.value } })}
/>
<FormFeedback>{errors && errors.examinationDate}</FormFeedback>
<FormFeedback>
<InputError>{errors && errors.examinationDate}</InputError>
</FormFeedback>
</Col>
<Col className="mt-4 text-center mt-md-0" sm="12" md="4">
<Label htmlFor="proofWithoutComplaint" className="mb-0">
......
import React, { useState } from "react"
import PropTypes from "prop-types"
import Link from "next/link"
import { Alert, Col, Container, FormFeedback, Input, Row } from "reactstrap"
import { withAuthentication, getCurrentUser, buildAuthHeaders, redirectIfUnauthorized } from "../utils/auth"
......@@ -99,8 +101,11 @@ const FillEmploymentsPage = ({
</p>
<p className="mb-5 text-center">
<small>
Les prochains mois, ces données seront pré-remplies et modifiables. Leur exactitude reste sous votre
responsabilité.
Attention, un ETP est un Equivalent Temps Plein et non un poste.{" "}
<Link href={"/faq"}>
<a>{"+ d'infos dans la FAQ"}</a>
</Link>
.
</small>
</p>
......@@ -114,8 +119,11 @@ const FillEmploymentsPage = ({
<Label htmlFor="doctors">Médecin</Label>
<Input
name="doctors"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.doctors}
placeholder="Nombre d'ETP"
placeholder="ex: 2,1"
value={(dataMonth && dataMonth["doctors"]) || ""}
onChange={(event) => handleChange(event, currentMonth)}
autoComplete="off"
......@@ -126,8 +134,11 @@ const FillEmploymentsPage = ({
<Label htmlFor="secretaries">Secrétaire</Label>
<Input
name="secretaries"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.secretaries}
placeholder="Nombre d'ETP"
placeholder="ex: 2,1"
value={(dataMonth && dataMonth["secretaries"]) || ""}
onChange={(event) => handleChange(event, currentMonth)}
autoComplete="off"
......@@ -138,8 +149,11 @@ const FillEmploymentsPage = ({
<Label htmlFor="nursings">Aide soignant.e</Label>
<Input
name="nursings"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.nursings}
placeholder="Nombre d'ETP"
placeholder="ex: 2,1"
value={(dataMonth && dataMonth["nursings"]) || ""}
onChange={(event) => handleChange(event, currentMonth)}
autoComplete="off"
......@@ -151,8 +165,11 @@ const FillEmploymentsPage = ({
<Label htmlFor="executives">Cadre de santé</Label>
<Input
name="executives"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.executives}
placeholder="Nombre d'ETP"
placeholder="ex: 2,1"
value={(dataMonth && dataMonth["executives"]) || ""}
onChange={(event) => handleChange(event, currentMonth)}
autoComplete="off"
......@@ -165,8 +182,11 @@ const FillEmploymentsPage = ({
<Label htmlFor="ides">IDE</Label>
<Input
name="ides"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.ides}
placeholder="Nombre d'ETP"
placeholder="ex: 2,1"
value={(dataMonth && dataMonth["ides"]) || ""}
onChange={(event) => handleChange(event, currentMonth)}
autoComplete="off"
......@@ -177,8 +197,11 @@ const FillEmploymentsPage = ({
<Label htmlFor="auditoriumAgents">{"Agent d'amphithéâtre"}</Label>
<Input
name="auditoriumAgents"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.auditoriumAgents}
placeholder="Nombre d'ETP"
placeholder="ex: 2,1"
value={(dataMonth && dataMonth["auditoriumAgents"]) || ""}
onChange={(event) => handleChange(event, currentMonth)}
autoComplete="off"
......@@ -189,8 +212,11 @@ const FillEmploymentsPage = ({
<Label htmlFor="others">Autres</Label>
<Input
name="others"
type="number"
min={0}
step="0.05"
invalid={errors && !!errors.others}
placeholder="Nombre d'ETP"
placeholder="ex: 2,1"
value={(dataMonth && dataMonth["others"]) || ""}
onChange={(event) => handleChange(event, currentMonth)}
autoComplete="off"
......
......@@ -20,7 +20,7 @@ const LoginPage = ({ message, welcomeMessage }) => {
const authentication = (userData) => {
isMounted = true
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
if (isMounted) setError("")
setTimeout(async () => {
......@@ -36,13 +36,9 @@ const LoginPage = ({ message, welcomeMessage }) => {
resolve("OK")
} catch (error) {