diff --git a/package.json b/package.json index 12cb0fd33..be4b25594 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "noharm-app", - "version": "3.1.16", + "version": "3.1.17", "private": true, "dependencies": { "@ckeditor/ckeditor5-build-classic": "^35.0.1", diff --git a/public/index.html b/public/index.html index 5a304be9f..eef22745e 100644 --- a/public/index.html +++ b/public/index.html @@ -99,6 +99,39 @@ + + diff --git a/src/components/DrugAlertLevelTag.jsx b/src/components/DrugAlertLevelTag.jsx new file mode 100644 index 000000000..0b32075a7 --- /dev/null +++ b/src/components/DrugAlertLevelTag.jsx @@ -0,0 +1,96 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; + +import Tooltip from "components/Tooltip"; +import Badge from "components/Badge"; +import Tag from "components/Tag"; + +export default function DrugAlertLevelTag({ + levels, + count, + showDescription = false, + allergy = false, + onClick = () => {}, +}) { + const { t } = useTranslation(); + + const getAlertStyle = () => { + const defaultColor = { + background: "#7ebe9a", + borderColor: "#7ebe9a", + color: "#fff", + }; + + if (!levels.length) { + return defaultColor; + } + + if (levels.indexOf("high") !== -1) { + return { + background: "#f44336", + borderColor: "#f44336", + color: "#fff", + }; + } + + if (levels.indexOf("medium") !== -1) { + return { + background: "#f57f17", + borderColor: "#f57f17", + color: "#fff", + }; + } + + if (levels.indexOf("low") !== -1) { + return { + background: "#ffc107", + borderColor: "#ffc107", + color: "#fff", + }; + } + }; + + const getAlertDescription = () => { + if (!levels.length) { + return "-"; + } + + if (levels.indexOf("high") !== -1) { + return "Alto"; + } + + if (levels.indexOf("medium") !== -1) { + return "Médio"; + } + + if (levels.indexOf("low") !== -1) { + return "Baixo"; + } + }; + + return ( + + + onClick()} + > + {showDescription ? getAlertDescription() : count} + + + + ); +} diff --git a/src/components/Forms/ClinicalNotes/util/templates.js b/src/components/Forms/ClinicalNotes/util/templates.js index 8a22c300e..e56ef08b3 100644 --- a/src/components/Forms/ClinicalNotes/util/templates.js +++ b/src/components/Forms/ClinicalNotes/util/templates.js @@ -346,11 +346,11 @@ export const alertsTemplate = (prescription) => { let alerts = []; list.forEach((i) => { - if (i.alerts && i.alerts.length) { + if (i.alertsComplete && i.alertsComplete.length) { const tpl = ` Medicamento: ${i.drug} Alertas: - -- ${i.alerts.join("\r\n -- ")} + -- ${i.alertsComplete.map((a) => a.text).join("\r\n -- ")} `; alerts = [...alerts, tpl]; } diff --git a/src/components/Layout/index.jsx b/src/components/Layout/index.jsx index 25d6c5968..f3090bb7a 100644 --- a/src/components/Layout/index.jsx +++ b/src/components/Layout/index.jsx @@ -139,6 +139,9 @@ const Me = ({ const ErrorFallback = ({ error, resetErrorBoundary }) => { console.error(error); + if (window.cwr) { + window.cwr("recordError", error); + } return (
- {t("alerts.alergy")} + {t("drugAlertType.allergy")} - - {t("alerts.max_dose")} + {t("drugAlertType.maxDose")} - - - {t("alerts.duplicate")} + + {t("drugAlertType.dm")} - - - {t("alerts.exam")} + + {t("drugAlertType.dt")} - - - {t("alerts.y")} + + {t("drugAlertType.liver")} - - - {t("alerts.isl")} + + {t("drugAlertType.iy")} - - - {t("alerts.interaction")} + + {t("drugAlertType.it")} - - - {t("alerts.tube")} + + {t("drugAlertType.ira")} + + + {t("drugAlertType.sl")} - - {t("alerts.elderly")} + {t("drugAlertType.elderly")} + + + {t("drugAlertType.kidney")} + + + {t("drugAlertType.platelets")} + + + {t("drugAlertType.rx")} + + + {t("drugAlertType.tube")} - - {t("alerts.time")} + {t("drugAlertType.maxTime")} diff --git a/src/components/Prioritization/Util/index.js b/src/components/Prioritization/Util/index.js index 3da715aad..342a2b51e 100644 --- a/src/components/Prioritization/Util/index.js +++ b/src/components/Prioritization/Util/index.js @@ -126,7 +126,8 @@ export const filterList = (list, filter) => { newList = newList.filter( (i) => i.namePatient.toLowerCase().includes(filter.searchKey) || - `${i.admissionNumber}`.includes(filter.searchKey) + `${i.admissionNumber}`.includes(filter.searchKey) || + `${i.idPatient}`.includes(filter.searchKey) ); } diff --git a/src/components/References/Relation/columns.jsx b/src/components/References/Relation/columns.jsx index 656f50500..b74814e78 100644 --- a/src/components/References/Relation/columns.jsx +++ b/src/components/References/Relation/columns.jsx @@ -56,6 +56,51 @@ const columns = (security) => [ ), }, + { + title: "Nível", + render: (entry, record) => { + switch (record.level) { + case "low": + return ( + + Baixo + + ); + case "medium": + return ( + + Médio + + ); + case "high": + return ( + + Alto + + ); + default: + return record.level; + } + }, + }, { title: "Ações", key: "operations", diff --git a/src/components/References/Relation/index.jsx b/src/components/References/Relation/index.jsx index c1547b811..f5613ceae 100644 --- a/src/components/References/Relation/index.jsx +++ b/src/components/References/Relation/index.jsx @@ -159,6 +159,33 @@ export default function Relation({ /> + {relation.item.editable && ( + + + + Nível: + + + + + + + )} {relation.item.editable && ( diff --git a/src/components/Screening/AlertCard/index.jsx b/src/components/Screening/AlertCard/index.jsx index ae709b2e1..f98fd7079 100644 --- a/src/components/Screening/AlertCard/index.jsx +++ b/src/components/Screening/AlertCard/index.jsx @@ -1,4 +1,5 @@ -import React from "react"; +import React, { useState } from "react"; +import { useDispatch } from "react-redux"; import { useTranslation } from "react-i18next"; import { ForkOutlined, @@ -9,6 +10,13 @@ import { import CustomIcon from "components/Icon"; import PrescriptionCard from "components/PrescriptionCard"; import Tooltip from "components/Tooltip"; +import DefaultModal from "components/Modal"; +import Button from "components/Button"; +import AlertListReport from "features/reports/AlertListReport/AlertListReport"; +import { + setInitialFilters, + setReportData, +} from "features/reports/AlertListReport/AlertListReportSlice"; import { AlertContainer } from "./index.style"; @@ -17,51 +25,62 @@ export const getAlerts = (stats, t) => [ label: t("alerts.y"), icon: () => , value: stats.inc + stats.isl, + filters: { typeList: ["iy", "sl"] }, }, { label: t("alerts.interaction"), icon: () => , value: stats.int, + filters: { typeList: ["it"] }, }, { label: t("alerts.max_dose"), icon: () => , value: stats.maxDose, + filters: { typeList: ["maxDose"] }, }, { label: t("alerts.exam"), icon: () => , value: stats.exams, + filters: { typeList: ["liver", "kidney", "platelets"] }, }, { label: t("alerts.time"), icon: () => , value: stats.maxTime, + filters: { typeList: ["time"] }, }, { label: t("alerts.elderly"), icon: () => , value: stats.elderly, + filters: { typeList: ["elderly"] }, }, { label: t("alerts.alergy"), icon: () => , - value: stats.allergy, + value: stats.allergy + (stats?.interactions?.rx || 0), + filters: { typeList: ["allergy", "rx"] }, }, { label: t("alerts.tube"), icon: () => , value: stats.tube, + filters: { typeList: ["tube"] }, }, { label: t("alerts.duplicate"), icon: () => , value: stats.dup, + filters: { typeList: ["dm", "dt"] }, }, ]; -export default function AlertCard({ stats }) { +export default function AlertCard({ stats, prescription }) { const { t } = useTranslation(); + const dispatch = useDispatch(); + const [modal, setModal] = useState(false); if (!stats) { return null; @@ -69,22 +88,46 @@ export default function AlertCard({ stats }) { const alerts = getAlerts(stats, t); + const openModal = (filters = {}) => { + dispatch(setInitialFilters(filters)); + dispatch(setReportData(prescription.alertsList)); + setModal(true); + }; + return (
-

{t("tableHeader.alerts")}

+

+ {t("tableHeader.alerts")} + +

{alerts.map((a) => ( -
0 ? "alert" : ""}> +
0 ? "alert" : ""} + onClick={() => openModal(a.filters)} + > {a.icon()} {a.value}
))}
+ setModal(false)} + width={"min(1440px, 100%)"} + footer={null} + style={{ top: "10px", height: "100vh" }} + > + + ); } diff --git a/src/components/Screening/AlertCard/index.style.jsx b/src/components/Screening/AlertCard/index.style.jsx index 870496604..6026c9a52 100644 --- a/src/components/Screening/AlertCard/index.style.jsx +++ b/src/components/Screening/AlertCard/index.style.jsx @@ -23,6 +23,7 @@ export const AlertContainer = styled.div` display: flex; align-items: center; justify-content: space-between; + cursor: pointer; @media only screen and (min-width: 1515px) { font-size: 16px; diff --git a/src/components/Screening/Patient/index.jsx b/src/components/Screening/Patient/index.jsx index 36898b15d..e4460959d 100644 --- a/src/components/Screening/Patient/index.jsx +++ b/src/components/Screening/Patient/index.jsx @@ -115,7 +115,7 @@ export default function Patient({ justifyContent: "space-between", }} > - + {hasClinicalNotes && (
span { + .panel-header-description { padding-left: 15px; - } - .p-number { - padding-right: 10px; - } + div > span { + padding-left: 15px; + } - a { - color: rgba(0, 0, 0, 0.65); - text-decoration: none; - } + .p-number { + padding-right: 10px; + } - a:hover { - text-decoration: underline; - } + a { + color: rgba(0, 0, 0, 0.65); + text-decoration: none; + } - .title { - font-size: 16px; - } + a:hover { + text-decoration: underline; + } - .subtitle { - opacity: 0.6; - } + .title { + font-size: 16px; + } - .expired { - color: rgb(207, 19, 34); + .subtitle { + opacity: 0.6; + } + + .expired { + color: rgb(207, 19, 34); + } } `; @@ -100,6 +104,8 @@ export const PrescriptionCollapse = styled(Collapse)` transition: background 0.3s linear; .ant-collapse-header { + align-items: center; + .panel-header { transition: transform 0.3s cubic-bezier(0.33, 1, 0.68, 1); } @@ -244,3 +250,47 @@ export const InterventionListContainer = styled.div` margin-top: 15px; } `; + +export const DrugAlertsCollapse = styled(Collapse)` + border: 1px solid #ffccc7 !important; + background: #fff2f0; + + .ant-collapse-item { + &.high { + .tag { + background: #f44336; + border-color: #f44336; + color: #fff; + } + } + + &.medium { + .tag { + background: #f57f17; + border-color: #f57f17; + color: #fff; + } + } + + &.low { + .tag { + background: #ffc107; + border-color: #ffc107; + color: #fff; + } + } + + .ant-collapse-header { + align-items: center; + padding: 12px 10px; + } + } + + .ant-collapse-content.ant-collapse-content-active { + padding-top: 0; + + .ant-collapse-content-box { + padding-left: 35px; + } + } +`; diff --git a/src/components/Screening/PrescriptionDrug/PrescriptionDrugList.jsx b/src/components/Screening/PrescriptionDrug/PrescriptionDrugList.jsx index 68bdf9007..2f4c5086e 100644 --- a/src/components/Screening/PrescriptionDrug/PrescriptionDrugList.jsx +++ b/src/components/Screening/PrescriptionDrug/PrescriptionDrugList.jsx @@ -16,6 +16,7 @@ import { import { filterInterventionByPrescriptionDrug } from "utils/transformers/intervention"; import SecurityService from "services/security"; import FeatureService from "services/features"; +import DrugAlertLevelTag from "components/DrugAlertLevelTag"; import { PrescriptionCollapse, @@ -70,8 +71,12 @@ export const rowClassName = (record, bag) => { classes.push("checked"); } + if (!record.checked && record.drug) { + classes.push("new-item"); + } + if (record.whiteList && !record.total) { - classes.push("checked"); + classes.push("whitelist"); } if (bag.interventions) { @@ -246,59 +251,95 @@ export default function PrescriptionDrugList({ ); }; - const panelHeader = (ds) => ( - -
- - {t("prescriptionDrugList.panelPrescription")}   - - # {ds.key} - - -
-
- - {t("prescriptionDrugList.panelIssueDate")}:   - {format(new Date(headers[ds.key].date), "dd/MM/yyyy HH:mm")} - - + const panelHeader = (ds) => { + const isChecked = headers[ds.key].status === "s"; + const source = isEmpty(ds.value) ? null : ds.value[0].source; + let summary = null; + if (headers[ds.key] && headers[ds.key][summarySourceToType(source)]) { + summary = headers[ds.key][summarySourceToType(source)]; + } + + return ( + +
+ {!isChecked && summary && summary.alerts > 0 && ( + + )} +
+
+
+ + {t("prescriptionDrugList.panelPrescription")}   + + # {ds.key} + + +
+
+ + {t("prescriptionDrugList.panelIssueDate")}:{" "} +   + {format(new Date(headers[ds.key].date), "dd/MM/yyyy HH:mm")} + + + {t("prescriptionDrugList.panelValidUntil")}:{" "} +   + + {headers[ds.key].expire + ? format(new Date(headers[ds.key].expire), "dd/MM/yyyy HH:mm") + : " - "} + + + + {t("prescriptionDrugList.panelBed")}:   + + {headers[ds.key].bed} + + + + {t("prescriptionDrugList.panelPrescriber")}:{" "} +   + {headers[ds.key].prescriber} + +
+
+
+ ); + }; + + const groupHeader = (dt, headerSummary) => { + return ( + + {!headerSummary.checked && + headerSummary && + headerSummary.summary.alerts > 0 && ( + + )} + {t("prescriptionDrugList.panelValidUntil")}:   - - {headers[ds.key].expire - ? format(new Date(headers[ds.key].expire), "dd/MM/yyyy HH:mm") - : " - "} + + {format(parseISO(dt), "dd/MM/yyyy")} - - {t("prescriptionDrugList.panelBed")}:   - - {headers[ds.key].bed} - - - - {t("prescriptionDrugList.panelPrescriber")}:   - {headers[ds.key].prescriber} - -
-
- ); - - const groupHeader = (dt) => ( - - - {t("prescriptionDrugList.panelValidUntil")}:   - - {format(parseISO(dt), "dd/MM/yyyy")} - - - - ); + + ); + }; const summarySourceToType = (s) => { switch (sourceToStoreType(s)) { @@ -306,6 +347,10 @@ export default function PrescriptionDrugList({ return "drugs"; case "solution": + if (featureService.hasDisableSolutionTab()) { + return "drugs"; + } + return "solutions"; case "procedure": return "procedures"; @@ -429,12 +474,13 @@ export default function PrescriptionDrugList({ const aggSummary = (currentData, addData) => { const baseData = currentData || { alerts: 0, - alergy: 0, + allergy: 0, interventions: 0, np: 0, am: 0, av: 0, controlled: 0, + alertLevel: [], }; if (isEmpty(addData)) { @@ -443,7 +489,11 @@ export default function PrescriptionDrugList({ const aggData = {}; Object.keys(baseData).forEach((k) => { - aggData[k] = baseData[k] + addData[k]; + if (k === "alertLevel") { + aggData[k] = [...baseData[k], addData[k]]; + } else { + aggData[k] = baseData[k] + addData[k]; + } }); return aggData; @@ -486,7 +536,7 @@ export default function PrescriptionDrugList({ const getCollapseItems = (g) => [ { key: "1", - label: groupHeader(g), + label: groupHeader(g, groups[g]), extra: groupSummary(groups[g]), className: groups[g].checked ? "checked" : "", children: list(groups[g].ids), diff --git a/src/components/Screening/PrescriptionDrug/components/DrugAlerts.jsx b/src/components/Screening/PrescriptionDrug/components/DrugAlerts.jsx new file mode 100644 index 000000000..93e12933d --- /dev/null +++ b/src/components/Screening/PrescriptionDrug/components/DrugAlerts.jsx @@ -0,0 +1,158 @@ +import React, { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { Alert, Tag } from "antd"; +import { + PlusOutlined, + MinusOutlined, + CloseCircleFilled, +} from "@ant-design/icons"; + +import RichTextView from "components/RichTextView"; +import Button from "components/Button"; +import Tooltip from "components/Tooltip"; +import { DrugAlertsCollapse } from "../PrescriptionDrug.style"; + +export default function DrugAlerts({ alerts, disableGroups }) { + const { t } = useTranslation(); + const [activeKey, setActiveKey] = useState([]); + + useEffect(() => { + if (alerts && alerts.length > 0) { + const levels = alerts.map((a) => a.level); + + if (levels.indexOf("medium") !== -1) { + setActiveKey(["medium"]); + } + + if (levels.indexOf("high") !== -1) { + setActiveKey(["high"]); + } + } + }, [alerts]); + + if (alerts == null || alerts.length === 0) { + return null; + } + + const activeKeyChange = (keys) => { + setActiveKey(keys); + }; + + const toggleAll = (groups) => { + if (activeKey.length) { + setActiveKey([]); + } else { + setActiveKey(Object.keys(groups)); + } + }; + + const drugAlertTitle = (type, group) => { + return ( +
+ <> + {group.length} + {t(`drugAlertType.${type}`)} + +
+ ); + }; + + const getIconColor = (type) => { + switch (type) { + case "high": + return "#f44336"; + case "medium": + return "#f57f17"; + case "low": + return "#ffc107"; + default: + return "#f44336"; + } + }; + + const groups = {}; + alerts.forEach((a) => { + let type = a.level; + + if (!groups.hasOwnProperty(type)) { + groups[type] = [a]; + } else { + groups[type].push(a); + } + }); + + const items = []; + + ["high", "medium", "low"].forEach((type) => { + if (groups[type]) { + items.push({ + key: type, + label: drugAlertTitle(type, groups[type]), + className: type, + children: ( + <> + {groups[type].map((item, index) => ( + } + style={{ marginTop: "5px", background: "#fff" }} + showIcon + icon={ + + } + /> + ))} + + ), + }); + } + }); + + if (disableGroups) { + return ( + <> + {alerts.map((item, index) => ( + } + style={{ marginTop: "5px" }} + showIcon + /> + ))} + + ); + } + + return ( +
+ +
+ ); +} diff --git a/src/components/Screening/PrescriptionDrug/components/Filters.jsx b/src/components/Screening/PrescriptionDrug/components/Filters.jsx index ba5ab206a..8f97e7388 100644 --- a/src/components/Screening/PrescriptionDrug/components/Filters.jsx +++ b/src/components/Screening/PrescriptionDrug/components/Filters.jsx @@ -15,6 +15,7 @@ import { setPrescriptionListOrder, savePreferences, } from "features/preferences/PreferencesSlice"; +import DrugAlertTypeEnum from "models/DrugAlertTypeEnum"; import { ToolBox } from "../PrescriptionDrug.style"; @@ -31,6 +32,42 @@ export default function Filters({ showPrescriptionOrder }) { const filterOptions = () => { const items = [ + { + key: "alertsLevelGroup", + label: t(`prescriptionDrugFilters.alertsLevelGroup`), + children: [ + { + key: "alertsAll_level", + label: t(`prescriptionDrugFilters.alertsAll`), + }, + { + key: "alertsHigh", + label: t(`prescriptionDrugFilters.alertsHigh`), + }, + { + key: "alertsMedium", + label: t(`prescriptionDrugFilters.alertsMedium`), + }, + { + key: "alertsLow", + label: t(`prescriptionDrugFilters.alertsLow`), + }, + ], + }, + { + key: "alertsTypeGroup", + label: t(`prescriptionDrugFilters.alertsTypeGroup`), + children: [ + { + key: "alertsAll_type", + label: t(`prescriptionDrugFilters.alertsAll`), + }, + ...DrugAlertTypeEnum.getAlertTypes(t).map((a) => ({ + key: `drugAlertType.${a.id}`, + label: a.label, + })), + ], + }, { key: "hv", label: t(`prescriptionDrugFilters.hv`), @@ -39,14 +76,12 @@ export default function Filters({ showPrescriptionOrder }) { key: "am", label: t(`prescriptionDrugFilters.am`), }, + { key: "active", label: t(`prescriptionDrugFilters.active`), }, - { - key: "alerts", - label: t(`prescriptionDrugFilters.alerts`), - }, + { key: "withValidation", label: t(`prescriptionDrugFilters.withValidation`), @@ -98,7 +133,7 @@ export default function Filters({ showPrescriptionOrder }) { closable onClose={() => handleFilterClick({ key: i })} > - {t(`prescriptionDrugFilters.${i}`)} + {t(i.split(".").length > 1 ? i : `prescriptionDrugFilters.${i}`)} ))}
diff --git a/src/components/Screening/PrescriptionDrug/components/PanelAction.jsx b/src/components/Screening/PrescriptionDrug/components/PanelAction.jsx index 3dbf3d4ac..ffbe12f18 100644 --- a/src/components/Screening/PrescriptionDrug/components/PanelAction.jsx +++ b/src/components/Screening/PrescriptionDrug/components/PanelAction.jsx @@ -1,4 +1,5 @@ import React from "react"; +import { useDispatch } from "react-redux"; import { useTranslation } from "react-i18next"; import { LinkOutlined, @@ -10,14 +11,13 @@ import { CopyOutlined, } from "@ant-design/icons"; -import Badge from "components/Badge"; import Dropdown from "components/Dropdown"; import Tooltip from "components/Tooltip"; import Button from "components/Button"; -import Tag from "components/Tag"; import notification from "components/notification"; import { sourceToStoreType } from "utils/transformers/prescriptions"; import { getErrorMessageFromException } from "utils/errorHandler"; +import { setCheckSummary } from "features/prescription/PrescriptionSlice"; const PanelAction = ({ id, @@ -31,6 +31,7 @@ const PanelAction = ({ hasPrescriptionEdit, }) => { const { t } = useTranslation(); + const dispatch = useDispatch(); const summarySourceToType = (s) => { switch (sourceToStoreType(s)) { @@ -71,25 +72,6 @@ const PanelAction = ({ const summaryTags = (summary) => { const tags = []; - if (summary.alerts) { - tags.push( - - - - {summary.alerts} - - - - ); - } - if (summary.interventions) { tags.push( { - checkScreening(id, status) - .then(() => { - notification.success({ - message: - status === "s" - ? "Checagem efetuada com sucesso!" - : "Checagem desfeita com sucesso!", - }); - }) - .catch((err) => { - notification.error({ - message: t("error.title"), - description: getErrorMessageFromException(err, t), + if (status === "s") { + dispatch( + setCheckSummary({ + idPrescription: id, + agg: false, + }) + ); + } else { + checkScreening(id, status) + .then(() => { + notification.success({ + message: + status === "s" + ? "Checagem efetuada com sucesso!" + : "Checagem desfeita com sucesso!", + }); + }) + .catch((err) => { + notification.error({ + message: t("error.title"), + description: getErrorMessageFromException(err, t), + }); }); - }); + } }; const handleMenuClick = ({ key, domEvent }) => { diff --git a/src/components/Screening/PrescriptionDrug/components/PresmedTags.jsx b/src/components/Screening/PrescriptionDrug/components/PresmedTags.jsx index 866c46778..e09c60394 100644 --- a/src/components/Screening/PrescriptionDrug/components/PresmedTags.jsx +++ b/src/components/Screening/PrescriptionDrug/components/PresmedTags.jsx @@ -9,15 +9,11 @@ import { import { useSelector } from "react-redux"; import { WarningOutlined, - CheckOutlined, - FormOutlined, - StopOutlined, MessageOutlined, HourglassOutlined, CalendarOutlined, } from "@ant-design/icons"; -import Badge from "components/Badge"; import Tooltip from "components/Tooltip"; import Tag from "components/Tag"; @@ -50,16 +46,6 @@ function PresmedTags({ prescription, bag }) { return ( - bag.handleRowExpand(prescription)} - > - {prescription.checked && ( - - - - )} - bag.handleRowExpand(prescription)} @@ -71,16 +57,6 @@ function PresmedTags({ prescription, bag }) { )} - bag.handleRowExpand(prescription)} - > - {prescription.prevNotes && prescription.prevNotes !== "None" && ( - - - - )} - bag.handleRowExpand(prescription)} @@ -99,16 +75,6 @@ function PresmedTags({ prescription, bag }) {
)} - bag.handleRowExpand(prescription)} - > - {prescription.suspended && ( - - - - )} - {hasExpireInfo && ( <> )} - bag.handleRowExpand(prescription)} - > - {!isEmpty(prescription.alerts) && ( - - - - {prescription.alerts.length} - - - - )} - ); } diff --git a/src/components/Screening/ScreeningActions/index.jsx b/src/components/Screening/ScreeningActions/index.jsx index 1b0950bd6..6b5e3327a 100644 --- a/src/components/Screening/ScreeningActions/index.jsx +++ b/src/components/Screening/ScreeningActions/index.jsx @@ -6,6 +6,8 @@ import { FloatButton } from "antd"; import FormPatientModal from "containers/Forms/Patient"; import { shouldUpdatePrescription } from "features/serverActions/ServerActionsSlice"; import InterventionOutcome from "features/intervention/InterventionOutcome/InterventionOutcome"; +import CheckSummary from "features/prescription/CheckSummary/CheckSummary"; +import SecurityService from "services/security"; import { ScreeningFloatButtonGroup } from "../index.style"; @@ -14,8 +16,12 @@ export default function ScreeningActions({ prescription, setModalVisibility, patientEditVisible, + roles, + checkScreening, + interventions, }) { const dispatch = useDispatch(); + const security = SecurityService(roles); const afterSavePatient = () => { dispatch( @@ -56,6 +62,14 @@ export default function ScreeningActions({ afterSavePatient={afterSavePatient} /> + + ); } diff --git a/src/components/Screening/columns.jsx b/src/components/Screening/columns.jsx index 20f603290..702d913fc 100644 --- a/src/components/Screening/columns.jsx +++ b/src/components/Screening/columns.jsx @@ -9,6 +9,8 @@ import { CaretDownOutlined, FormOutlined, CalculatorOutlined, + CheckCircleOutlined, + StopOutlined, } from "@ant-design/icons"; import { Button as AntButton } from "antd"; @@ -20,16 +22,17 @@ import Descriptions from "components/Descriptions"; import Tag from "components/Tag"; import { createSlug } from "utils/transformers/utils"; import Dropdown from "components/Dropdown"; -import Alert from "components/Alert"; import RichTextView from "components/RichTextView"; import InterventionStatus from "models/InterventionStatus"; import { SelectMultiline } from "components/Inputs"; import { filterInterventionByPrescriptionDrug } from "utils/transformers/intervention"; import { setSelectedIntervention as setSelectedInterventionOutcome } from "features/intervention/InterventionOutcome/InterventionOutcomeSlice"; +import DrugAlertLevelTag from "components/DrugAlertLevelTag"; import { PeriodTags } from "./index.style"; import SolutionCalculator from "./PrescriptionDrug/components/SolutionCalculator"; import PresmedTags from "./PrescriptionDrug/components/PresmedTags"; +import DrugAlerts from "./PrescriptionDrug/components/DrugAlerts"; import { InterventionView } from "./Intervention/columns"; import DrugForm from "./Form"; @@ -333,7 +336,8 @@ const NestedTableContainer = styled.div` .ant-descriptions-item-label { font-weight: 600; color: #2e3c5a; - width: 20%; + width: 15%; + text-align: right; } `; @@ -359,26 +363,6 @@ const periodDates = (dates) => { ); }; -const showAlerts = (alerts) => { - if (alerts == null || alerts.length === 0) { - return "--"; - } - - return ( - <> - {alerts.map((item, index) => ( - } - style={{ marginTop: "5px" }} - showIcon - /> - ))} - - ); -}; - const periodDatesList = (dates) => { if (dates == null || dates.length === 0) { return ""; @@ -472,12 +456,15 @@ export const expandedRowRender = (bag) => (record) => { className={`${record.source} ${record.groupRow ? "group" : ""}`} > - {!isEmpty(record.alerts) && ( + {!isEmpty(record.alertsComplete) && ( - {showAlerts(record.alerts)} + )} {bag.security.hasPresmedForm() && bag.formTemplate && ( @@ -791,25 +778,89 @@ const drugInfo = (bag) => [ { key: "idPrescriptionDrug", dataIndex: "score", - width: 20, + width: 85, align: "center", - render: (entry, { score, near, total, emptyRow }) => { - if (total || emptyRow) { + render: (entry, prescription) => { + if (prescription.total || prescription.emptyRow) { return ""; } return ( - - - {score} - - + a.level) + : [] + } + count={prescription.alertsComplete?.length} + allergy={prescription.allergy} + onClick={() => bag.handleRowExpand(prescription)} + /> + + bag.handleRowExpand(prescription)} + > + {prescription.score} + + + {prescription.suspensionDate ? ( + + + + ) : ( + <> + {prescription.checked && ( + + + + )} + {!prescription.checked && ( + +
+ + )} + + )} +
); }, }, @@ -922,8 +973,8 @@ const frequencyAndTime = (bag) => [ { title: bag.t("tableHeader.time"), dataIndex: "time", - ellipsis: bag.condensed, - align: bag.condensed ? "left" : "center", + ellipsis: true, + align: "left", width: 100, render: (text, prescription) => { return {prescription.time}; diff --git a/src/components/Screening/index.jsx b/src/components/Screening/index.jsx index 45242260f..d5cea01f0 100644 --- a/src/components/Screening/index.jsx +++ b/src/components/Screening/index.jsx @@ -12,6 +12,8 @@ import Tag from "components/Tag"; import notification from "components/notification"; import Dropdown from "components/Dropdown"; import Menu from "components/Menu"; +import FeatureService from "services/features"; +import SecurityService from "services/security"; import PrescriptionList from "containers/Screening/PrescriptionDrug/PrescriptionList"; import SolutionList from "containers/Screening/PrescriptionDrug/SolutionList"; @@ -39,9 +41,9 @@ export default function Screening({ content, error, selectPrescriptionDrug, - security, interventions, - featureService, + roles, + features, }) { const params = useParams(); const id = params?.slug; @@ -49,6 +51,8 @@ export default function Screening({ content; const { t } = useTranslation(); + const security = SecurityService(roles); + const featureService = FeatureService(features); // show message if has error useEffect(() => { diff --git a/src/components/ScreeningList/columns.jsx b/src/components/ScreeningList/columns.jsx index 10ea650ec..045c5581e 100644 --- a/src/components/ScreeningList/columns.jsx +++ b/src/components/ScreeningList/columns.jsx @@ -1,16 +1,12 @@ import React from "react"; import styled from "styled-components/macro"; -import { - CheckOutlined, - SearchOutlined, - LoadingOutlined, -} from "@ant-design/icons"; +import { SearchOutlined, LoadingOutlined } from "@ant-design/icons"; -import { Link, BasicButton } from "components/Button"; +import { Link } from "components/Button"; import { InfoIcon } from "components/Icon"; import Tooltip from "components/Tooltip"; import Table from "components/Table"; -import PopConfirm from "components/PopConfirm"; +import Tag from "components/Tag"; const setDataIndex = (list) => list.map(({ key, ...column }) => ({ @@ -59,10 +55,6 @@ const ActionsBox = styled.div` } `; -const CheckedBox = styled.span` - padding: 5px 15px; -`; - const NestedTableContainer = styled.div` margin-top: 5px; margin-bottom: 35px; @@ -77,42 +69,8 @@ const ScreeningActions = ({ prioritizationType, t, }) => { - const checkAction = () => checkScreening(idPrescription, "s"); - - const isDisabled = - check.idPrescription !== idPrescription && check.isChecking; - const isChecking = - check.idPrescription === idPrescription && check.isChecking; - const isChecked = status === "s"; - return ( - {!isChecked && ( - - - - - - - - )} - {isChecked && ( - - - - - - )} { ), className: `gtm-th-idade ${oddClass(index++)}`, key: "age", - width: 30, + width: 40, align: "center", sortDirections, sorter: (a, b) => a.birthdays - b.birthdays, @@ -450,7 +408,8 @@ const columns = (sortedInfo, filteredInfo, t) => { filteredValue: filteredInfo.searchKey || null, onFilter: (value, record) => record.namePatient.toLowerCase().includes(value) || - `${record.admissionNumber}` === value, + `${record.admissionNumber}`.includes(value) || + `${record.idPatient}`.includes(value), sortDirections, sorter: (a, b) => Date.parse(a.date) - Date.parse(b.date), sortOrder: sortedInfo.columnKey === "date" && sortedInfo.order, @@ -464,10 +423,43 @@ const columns = (sortedInfo, filteredInfo, t) => { title: t("screeningList.prescriptionRisk"), children: setDataIndex(prescriptionRiskColumns), }, + { + title: t("labels.status"), + key: "status", + width: 0, + align: "center", + render: (text, prescription) => { + if (prescription.status === "s") { + return ( + + Checada + + ); + } + + if (prescription.status !== "s") { + return ( + <> + {prescription.isBeingEvaluated ? ( + + + Em análise + + + ) : ( + + Pendente + + )} + + ); + } + }, + }, { title: t("screeningList.actions"), key: "operations", - width: 70, + width: 10, align: "center", filteredValue: filteredInfo.status || null, onFilter: (value, record) => record.status === value, diff --git a/src/components/Table.jsx b/src/components/Table.jsx index 17ee1cd20..d727a562f 100644 --- a/src/components/Table.jsx +++ b/src/components/Table.jsx @@ -19,11 +19,19 @@ const Table = styled(AntTable)` padding: 5px; } + .ant-table-tbody > tr > td { + color: rgba(0, 0, 0, 0.65); + } + .ant-table-thead > tr > th, .ant-table-thead > tr > td { background: transparent; } + .ant-table-expand-icon-col { + width: 35px; + } + .ant-table-column-title { color: ${get("colors.primary")}; font-weight: ${get("weight.semiBold")}; @@ -62,18 +70,18 @@ const Table = styled(AntTable)` } &.red { - background-color: #e46666; + background-color: #f44336; color: #fff; } &.orange { - background-color: #e67e22; + background-color: #f57f17; color: #fff; } &.yellow { - background-color: #e4da66; - color: #000; + background-color: #ffc107; + color: #fff; } &.green { @@ -123,11 +131,53 @@ const Table = styled(AntTable)` opacity: 0.45; text-decoration: line-through; } + + .score-container { + transition: opacity 0.3s linear; + opacity: 0; + } + + &:hover { + .score-container { + opacity: 1; + } + } } .checked { + td:not(:nth-child(1)) { + opacity: 0.75; + } + } + + .new-item { + td:not(:nth-child(1)) { + font-weight: 500; + + a { + font-weight: 500; + } + } + } + + .whitelist { + .score-container { + transition: opacity 0.3s linear; + opacity: 0; + } + td:not(:nth-child(1)) { opacity: 0.45; + + a { + opacity: 0.45; + } + } + + &:hover { + .score-container { + opacity: 1; + } } } @@ -289,7 +339,7 @@ const Table = styled(AntTable)` .summary-row { td:not(:first-child) { - opacity: 0.2; + opacity: 0.45; } td { diff --git a/src/containers/Screening/PageHeader.jsx b/src/containers/Screening/PageHeader.jsx index 811a1af96..31f963a5d 100644 --- a/src/containers/Screening/PageHeader.jsx +++ b/src/containers/Screening/PageHeader.jsx @@ -1,15 +1,12 @@ import { connect } from "react-redux"; import { bindActionCreators } from "redux"; -import security from "services/security"; - import { checkScreeningThunk, reviewPatientThunk, incrementClinicalNotesThunk, } from "store/ducks/prescriptions/thunk"; import PageHeader from "pages/Screening/PageHeader"; -import FeatureService from "services/features"; const mapStateToProps = ({ prescriptions, user }) => ({ prescription: { @@ -19,8 +16,8 @@ const mapStateToProps = ({ prescriptions, user }) => ({ content: prescriptions.single.data, }, userId: user.account.userId, - security: security(user.account.roles), - featureService: FeatureService(user.account.features), + roles: user.account.roles, + features: user.account.features, }); const mapDispatchToProps = (dispatch) => bindActionCreators( diff --git a/src/containers/Screening/ScreeningActions.jsx b/src/containers/Screening/ScreeningActions.jsx index faf60966f..ff121abce 100644 --- a/src/containers/Screening/ScreeningActions.jsx +++ b/src/containers/Screening/ScreeningActions.jsx @@ -4,21 +4,22 @@ import { bindActionCreators } from "redux"; import { fetchScreeningThunk, setModalVisibilityThunk, + checkScreeningThunk, } from "store/ducks/prescriptions/thunk"; import ScreeningActions from "components/Screening/ScreeningActions"; -import security from "services/security"; -import FeatureService from "services/features"; const mapStateToProps = ({ prescriptions, user }) => ({ prescription: prescriptions.single.data, - security: security(user.account.roles), - featureService: FeatureService(user.account.features), patientEditVisible: prescriptions.single.actions.modalVisibility.patientEdit, + roles: user.account.roles, + features: user.account.features, + interventions: prescriptions.single.intervention.list, }); const mapDispatchToProps = (dispatch) => bindActionCreators( { fetchScreening: fetchScreeningThunk, + checkScreening: checkScreeningThunk, setModalVisibility: setModalVisibilityThunk, }, dispatch diff --git a/src/containers/Screening/index.jsx b/src/containers/Screening/index.jsx index 27de9802e..54ec6b96c 100644 --- a/src/containers/Screening/index.jsx +++ b/src/containers/Screening/index.jsx @@ -4,8 +4,6 @@ import { bindActionCreators } from "redux"; import { fetchScreeningThunk } from "store/ducks/prescriptions/thunk"; import { selectPrescriptionDrugThunk } from "store/ducks/prescriptionDrugs/thunk"; -import security from "services/security"; -import FeatureService from "services/features"; import Screening from "components/Screening"; const mapStateToProps = ({ prescriptions, user }) => ({ @@ -14,8 +12,8 @@ const mapStateToProps = ({ prescriptions, user }) => ({ isFetching: prescriptions.single.isFetching, content: prescriptions.single.data, interventions: prescriptions.single.intervention.list, - security: security(user.account.roles), - featureService: FeatureService(user.account.features), + roles: user.account.roles, + features: user.account.features, }); const mapDispatchToProps = (dispatch) => bindActionCreators( diff --git a/src/features/intervention/InterventionOutcome/Form/InterventionOutcomeForm.jsx b/src/features/intervention/InterventionOutcome/Form/InterventionOutcomeForm.jsx index 38f7ed1de..83956616c 100644 --- a/src/features/intervention/InterventionOutcome/Form/InterventionOutcomeForm.jsx +++ b/src/features/intervention/InterventionOutcome/Form/InterventionOutcomeForm.jsx @@ -133,7 +133,7 @@ export default function InterventionOutcomeForm() { return "error"; } - if (!Big(values[source][f]).eq(Big(original.item[f]))) { + if (!Big(values[source][f] || 0).eq(Big(original.item[f] || 0))) { return "warning"; } diff --git a/src/features/prescription/CheckSummary/CheckSummary.jsx b/src/features/prescription/CheckSummary/CheckSummary.jsx new file mode 100644 index 000000000..cca084512 --- /dev/null +++ b/src/features/prescription/CheckSummary/CheckSummary.jsx @@ -0,0 +1,344 @@ +import React, { useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { Alert, Tag } from "antd"; +import { Formik } from "formik"; +import * as Yup from "yup"; +import { useTranslation } from "react-i18next"; +import { + ExclamationCircleFilled, + CheckOutlined, + WarningOutlined, +} from "@ant-design/icons"; + +import RichTextView from "components/RichTextView"; +import Tooltip from "components/Tooltip"; +import DefaultModal from "components/Modal"; +import Heading from "components/Heading"; +import { Checkbox } from "components/Inputs"; +import notification from "components/notification"; +import { getErrorMessageFromException } from "utils/errorHandler"; +import { formatDate } from "utils/date"; +import { setCheckSummary } from "../PrescriptionSlice"; + +import { Form } from "styles/Form.style"; +import { DrugAlertsCollapse } from "components/Screening/PrescriptionDrug/PrescriptionDrug.style"; + +export default function CheckSummary({ + hasCpoe, + checkScreening, + headers, + alerts, + interventions, +}) { + const dispatch = useDispatch(); + const prescription = useSelector( + (state) => state.prescriptionv2.checkSummary.prescription + ); + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + + const validationSchema = Yup.object().shape({ + accept: Yup.boolean().oneOf( + [true], + "Você precisa aceitar para confirmar a checagem" + ), + }); + + if (!prescription) { + return null; + } + + let highRiskAlerts = []; + + if (alerts && alerts.length) { + if (prescription.agg) { + highRiskAlerts = alerts.filter((a) => a.level === "high"); + } else { + highRiskAlerts = alerts.filter( + (a) => + a.level === "high" && a.idPrescription === prescription.idPrescription + ); + } + } + + const initialValues = { + accept: highRiskAlerts.length > 0 ? false : true, + }; + + const onCancel = () => { + setLoading(false); + dispatch(setCheckSummary(null)); + }; + + const getHeader = (item) => { + return ( +
+
+ {item.drugName} +
+
+ ); + }; + + const getExtra = (item) => { + return ( +
+ + + {" "} + {item.dose}{" "} + {item.measureUnit?.label + ? item.measureUnit.label + : item.measureUnit.value} + + + + + {item.frequency?.label + ? item.frequency.label + : item.frequency.value} + + + + {item.route} + +
+ ); + }; + + const onSave = () => { + setLoading(true); + const auditAlerts = highRiskAlerts.map(({ idPrescriptionDrug, type }) => ({ + idPrescriptionDrug, + type, + })); + + checkScreening(prescription.idPrescription, "s", { + alerts: auditAlerts, + }) + .then(() => { + setLoading(false); + dispatch(setCheckSummary(null)); + notification.success({ + message: "Checagem efetuada com sucesso!", + }); + }) + .catch((err) => { + setLoading(false); + console.error("error", err); + notification.error({ + message: t("error.title"), + description: getErrorMessageFromException(err, t), + }); + }); + }; + + const groups = {}; + highRiskAlerts.forEach((a) => { + if (groups[a.idPrescriptionDrug]) { + groups[a.idPrescriptionDrug]["alerts"].push(a); + } else { + groups[a.idPrescriptionDrug] = { + ...a, + alerts: [a], + }; + } + }); + + const dateGroups = {}; + if (prescription?.agg && !hasCpoe) { + Object.keys(groups).forEach((g) => { + const dt = groups[g].expire + ? groups[g].expire.substr(0, 10) + : groups[g].date.substr(0, 10); + + if (dateGroups[dt]) { + dateGroups[dt].push(groups[g]); + } else { + dateGroups[dt] = [groups[g]]; + } + }); + } else { + dateGroups["uniq"] = []; + Object.keys(groups).forEach((g) => { + dateGroups["uniq"].push(groups[g]); + }); + } + + const AlertStatus = ({ idPrescription, idPrescriptionDrug }) => { + const header = headers[idPrescription]; + const intervention = interventions.find( + (i) => i.id === idPrescriptionDrug && i.status !== "0" + ); + + return ( + <> + {header && header.status === "s" && ( +
+ {" "} + {header.user ? <>Checado por {header.user} : <>Checado} +
+ )} + {intervention && ( +
+ {" "} + {intervention.user ? ( + <>Intervenção registrada por {intervention.user} + ) : ( + <>Intervenção registrada + )} +
+ )} + + ); + }; + + const getItemsByGroup = (g) => { + return { + key: g.idPrescriptionDrug, + label: getHeader(g), + extra: getExtra(g), + children: ( + <> + {g.alerts.map((a, index) => ( + } + style={{ marginTop: "5px", background: "#fff" }} + showIcon + /> + ))} + + + ), + }; + }; + + return ( + + {({ handleSubmit, errors, values, setFieldValue }) => ( + 0 ? 900 : 350} + centered + destroyOnClose + onCancel={() => onCancel()} + onOk={handleSubmit} + okText="Confirmar" + cancelText={t("actions.cancel")} + confirmLoading={loading} + okButtonProps={{ + disabled: loading, + }} + cancelButtonProps={{ + disabled: loading, + }} + maskClosable={false} + > +
+ + {" "} + Confirmar a checagem + +
+ {highRiskAlerts.length > 0 && ( + <> +

+ Revise os alertas de Nível Alto e confirme para + finalizar a checagem. +

+ +
+
+ {Object.keys(dateGroups) + .sort() + .map((dt, index) => ( + + {dt !== "uniq" && ( +
0 ? "20px" : 0, + padding: "6px 2px 0", + zIndex: 99, + background: "#fafafa", + color: "#2e3c5a", + }} + > + Fim da vigência: {formatDate(dt)} +
+ )} + + + getItemsByGroup(group) + )} + defaultActiveKey={dateGroups[dt].map( + (i) => i.idPrescriptionDrug + )} + style={{ marginBottom: "10px" }} + /> +
+ ))} +
+
+
+ + setFieldValue("accept", !target.value) + } + > + Estou ciente dos alertas apresentados. + +
+ {errors.accept && ( +
{errors.accept}
+ )} +
+
+ + )} +
+ )} +
+ ); +} diff --git a/src/features/prescription/PrescriptionSlice.js b/src/features/prescription/PrescriptionSlice.js index 567f94d18..243f4674e 100644 --- a/src/features/prescription/PrescriptionSlice.js +++ b/src/features/prescription/PrescriptionSlice.js @@ -9,6 +9,9 @@ const initialState = { data: {}, active: false, }, + checkSummary: { + prescription: null, + }, filters: [], }; @@ -35,6 +38,9 @@ const prescriptionSlice = createSlice({ setPrescriptionFilters(state, action) { state.filters = action.payload; }, + setCheckSummary(state, action) { + state.checkSummary.prescription = action.payload; + }, }, extraReducers(builder) { builder @@ -51,6 +57,7 @@ const prescriptionSlice = createSlice({ }, }); -export const { reset, setPrescriptionFilters } = prescriptionSlice.actions; +export const { reset, setPrescriptionFilters, setCheckSummary } = + prescriptionSlice.actions; export default prescriptionSlice.reducer; diff --git a/src/features/reports/AlertListReport/AlertList/AlertList.jsx b/src/features/reports/AlertListReport/AlertList/AlertList.jsx new file mode 100644 index 000000000..b1ded081b --- /dev/null +++ b/src/features/reports/AlertListReport/AlertList/AlertList.jsx @@ -0,0 +1,149 @@ +import React, { useState } from "react"; +import { useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; +import { Descriptions, Alert } from "antd"; + +import { CardTable } from "components/Table"; +import DrugAlertLevelTag from "components/DrugAlertLevelTag"; +import RichTextView from "components/RichTextView"; +import { formatDateTime } from "utils/date"; + +export default function AlertList() { + const { t } = useTranslation(); + const [expandedRows, setExpandedRows] = useState([]); + const datasource = useSelector( + (state) => state.reportsArea.alertList.filtered.result.list + ); + + const updateExpandedRows = (list, key) => { + if (list.includes(key)) { + return list.filter((i) => i !== key); + } + + return [...list, key]; + }; + + const handleRowExpand = (record) => { + setExpandedRows(updateExpandedRows(expandedRows, record.rowKey)); + }; + + const toggleExpansion = () => { + if (expandedRows.length) { + setExpandedRows([]); + } else { + setExpandedRows(datasource.map((i) => i.rowKey)); + } + }; + + const ExpandColumn = ({ expand }) => { + return ( +
+ +
+ ); + }; + + const columns = [ + { + title: "Nível", + align: "center", + render: (_, record) => { + return ( + + ); + }, + }, + { + title: "Medicamento", + render: (_, record) => record.drugName, + }, + + { + title: "Tipo", + render: (_, record) => t(`drugAlertType.${record.type}`), + }, + ]; + + return ( + <> + ( +
+ {datasource.length} registro(s) +
+ )} + expandable={{ + expandedRowRender: (record) => , + onExpand: (expanded, record) => handleRowExpand(record), + expandedRowKeys: expandedRows, + }} + onRow={(record) => { + return { + onClick: (event) => { + handleRowExpand(record); + }, + }; + }} + columnTitle={} + size="small" + pagination={{ showSizeChanger: true }} + /> + + ); +} + +const ExpandedRow = ({ record }) => { + const items = [ + { + label: "Dose", + span: 3, + children: `${record.dose} ${record.measureUnit?.label || "-"}`, + }, + { + label: "Frequência", + span: 3, + children: `${record.frequency?.label || "-"}`, + }, + { + label: "Via", + span: 3, + children: `${record.route || "-"}`, + }, + { + label: "Vigência", + span: 3, + children: `${ + record.expire + ? formatDateTime(record.expire) + : "Manter até segunda ordem" + }`, + }, + { + label: "Alerta", + span: 3, + children: ( + } + showIcon + /> + ), + }, + ]; + + return ; +}; diff --git a/src/features/reports/AlertListReport/AlertListReport.jsx b/src/features/reports/AlertListReport/AlertListReport.jsx new file mode 100644 index 000000000..e40a7751f --- /dev/null +++ b/src/features/reports/AlertListReport/AlertListReport.jsx @@ -0,0 +1,100 @@ +import React, { useRef } from "react"; +import { useSelector } from "react-redux"; +import { Row, Col, Space, Spin } from "antd"; + +import { PageHeader } from "styles/PageHeader.style"; +import { + ReportContainer, + ReportHeader, + ReportFilterContainer, + ReportPrintDescriptions, +} from "styles/Report.style"; +import Filter from "./Filter/Filter"; +import { ReactComponent as Brand } from "assets/noHarm-horizontal.svg"; +import { filtersToDescription } from "utils/report"; +import AlertList from "./AlertList/AlertList"; + +export default function AlertListReport({ prescription }) { + const status = useSelector((state) => state.reportsArea.alertList.status); + const filteredStatus = useSelector( + (state) => state.reportsArea.alertList.filtered.status + ); + const filters = useSelector((state) => state.reportsArea.alertList.filters); + const printRef = useRef(null); + const isLoading = status === "loading" || filteredStatus === "loading"; + const filtersConfig = { + drugList: { + label: "Medicamento", + type: "list", + }, + typeList: { + label: "Tipo", + type: "list", + }, + levelList: { + label: "Nível", + type: "list", + }, + }; + + return ( + <> + +
+

Relatório: Alertas

+
+
+ + + + +
+ +

Relatório: Alertas

+
+ +
+
+ + +
+
+ +
+ : {prescription.admissionNumber} +
+
+ : {prescription.namePatient} +
+
+ : {prescription.birthdateFormat} +
+
+ + + + + + + + + + + +
+
+
+ *A quantidade de alertas de interação e duplicidade é maior no + relatório, pois o alerta é contabilizado nas duas direções da interação. +
+ + ); +} diff --git a/src/features/reports/AlertListReport/AlertListReportSlice.js b/src/features/reports/AlertListReport/AlertListReportSlice.js new file mode 100644 index 000000000..912fda253 --- /dev/null +++ b/src/features/reports/AlertListReport/AlertListReportSlice.js @@ -0,0 +1,58 @@ +import { createSlice } from "@reduxjs/toolkit"; + +import { getUniqList } from "utils/report"; + +const initialState = { + status: "idle", + error: null, + list: [], + filters: {}, + filtered: { + status: "idle", + error: null, + result: { + list: [], + }, + }, + filterData: { + drugs: [], + }, + initialFilters: {}, +}; + +const alertListReportSlice = createSlice({ + name: "alertListReport", + initialState, + reducers: { + reset() { + return initialState; + }, + setInitialFilters(state, action) { + state.initialFilters = action.payload; + }, + setFilters(state, action) { + state.filters = action.payload; + }, + setFilteredStatus(state, action) { + state.filtered.status = action.payload; + }, + setFilteredResult(state, action) { + state.filtered.result = action.payload; + }, + setReportData(state, action) { + state.list = action.payload; + state.filterData.drugs = getUniqList(action.payload, "drugName"); + }, + }, +}); + +export const { + reset, + setFilters, + setFilteredResult, + setFilteredStatus, + setReportData, + setInitialFilters, +} = alertListReportSlice.actions; + +export default alertListReportSlice.reducer; diff --git a/src/features/reports/AlertListReport/Filter/Filter.jsx b/src/features/reports/AlertListReport/Filter/Filter.jsx new file mode 100644 index 000000000..d191f497b --- /dev/null +++ b/src/features/reports/AlertListReport/Filter/Filter.jsx @@ -0,0 +1,94 @@ +import React, { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { FloatButton, Spin } from "antd"; +import { MenuOutlined, PrinterOutlined } from "@ant-design/icons"; +import { useReactToPrint } from "react-to-print"; + +import { FloatButtonGroup } from "components/FloatButton"; +import AdvancedFilter from "components/AdvancedFilter"; +import { + setFilteredStatus, + setFilteredResult, + setFilters, +} from "../AlertListReportSlice"; +import { getReportData } from "../transformers"; +import MainFilters from "./MainFilters"; +import { onBeforePrint, onAfterPrint } from "utils/report"; + +export default function Filter({ printRef }) { + const dispatch = useDispatch(); + const isFetching = + useSelector((state) => state.reportsArea.alertList.status) === "loading"; + const datasource = useSelector((state) => state.reportsArea.alertList.list); + const initialFilters = useSelector( + (state) => state.reportsArea.alertList.initialFilters + ); + const handlePrint = useReactToPrint({ + content: () => printRef.current, + onBeforeGetContent: onBeforePrint, + onAfterPrint: onAfterPrint, + }); + const initialValues = { + levelList: [], + drugList: [], + typeList: [], + ...initialFilters, + }; + + useEffect(() => { + search({ + ...initialValues, + }); + }, []); //eslint-disable-line + + const search = async (params, forceDs) => { + let ds = []; + if (!forceDs) { + ds = datasource; + } + + dispatch(setFilteredStatus("loading")); + dispatch(setFilters(params)); + const reportData = getReportData(forceDs || ds, params); + dispatch(setFilteredResult(reportData)); + + setTimeout(() => { + dispatch(setFilteredStatus("succeeded")); + }, 500); + }; + + return ( + + + {!isFetching && ( + } + onSearch={search} + loading={isFetching} + skipFilterList={["dateRange", "segmentList", "departmentList"]} + /> + )} + + {!isFetching && ( + } + tooltip="Menu" + style={{ bottom: 25 }} + > + } + onClick={handlePrint} + tooltip="Imprimir" + /> + + )} + + + ); +} diff --git a/src/features/reports/AlertListReport/Filter/MainFilters.jsx b/src/features/reports/AlertListReport/Filter/MainFilters.jsx new file mode 100644 index 000000000..1ee13af0f --- /dev/null +++ b/src/features/reports/AlertListReport/Filter/MainFilters.jsx @@ -0,0 +1,99 @@ +import React, { useContext } from "react"; +import { useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; + +import { Select } from "components/Inputs"; +import Heading from "components/Heading"; +import { Col } from "components/Grid"; +import { AdvancedFilterContext } from "components/AdvancedFilter"; +import DrugAlertTypeEnum from "models/DrugAlertTypeEnum"; +import DrugAlertLevelTag from "components/DrugAlertLevelTag"; + +export default function MainFilters() { + const { t } = useTranslation(); + const drugs = useSelector( + (state) => state.reportsArea.alertList.filterData.drugs + ); + + const status = useSelector((state) => state.reportsArea.alertList.status); + const { values, setFieldValue } = useContext(AdvancedFilterContext); + + return ( + <> + + + Nível: + + + + + + Medicamento: + + + + + + + Tipo: + + + + + ); +} diff --git a/src/features/reports/AlertListReport/transformers.js b/src/features/reports/AlertListReport/transformers.js new file mode 100644 index 000000000..52c5bab53 --- /dev/null +++ b/src/features/reports/AlertListReport/transformers.js @@ -0,0 +1,42 @@ +import { exportCSV } from "utils/report"; + +const filterDatasource = (datasource, filters) => { + return datasource + .filter((i) => { + if (filters.drugList.length) { + return filters.drugList.indexOf(i.drugName) !== -1; + } + + return true; + }) + .filter((i) => { + if (filters.levelList.length) { + return filters.levelList.indexOf(i.level) !== -1; + } + + return true; + }) + .filter((i) => { + if (filters.typeList.length) { + return filters.typeList.indexOf(i.type) !== -1; + } + + return true; + }); +}; + +export const getReportData = (datasource, filters) => { + const filteredList = filterDatasource(datasource, filters); + + const reportData = { + list: filteredList, + }; + + return reportData; +}; + +export const filterAndExportCSV = (datasource, filters, t) => { + const items = filterDatasource(datasource, filters); + + return exportCSV(items, t); +}; diff --git a/src/features/userAdmin/UserAdminSlice.js b/src/features/userAdmin/UserAdminSlice.js index 506237574..4894c8dab 100644 --- a/src/features/userAdmin/UserAdminSlice.js +++ b/src/features/userAdmin/UserAdminSlice.js @@ -68,7 +68,6 @@ const userAdminSlice = createSlice({ state.single.status = "succeeded"; const item = action.payload.data.data; - console.log("item", item); const index = state.list.findIndex((i) => i.id === item.id); if (index !== -1) { diff --git a/src/lib/withAuth.jsx b/src/lib/withAuth.jsx index 31e80b4d0..cdfa88515 100644 --- a/src/lib/withAuth.jsx +++ b/src/lib/withAuth.jsx @@ -41,6 +41,16 @@ const AuthHandler = ({ return ; } + try { + if (window.cwr) { + window.cwr("addSessionAttributes", { + schema: localStorage.getItem("schema"), + }); + } + } catch (ex) { + console.error("cwr set schema error"); + } + return ; }; diff --git a/src/models/DrugAlertTypeEnum.js b/src/models/DrugAlertTypeEnum.js new file mode 100644 index 000000000..63b3d83a7 --- /dev/null +++ b/src/models/DrugAlertTypeEnum.js @@ -0,0 +1,85 @@ +export default class DrugAlertTypeEnum { + static ALLERGY = "allergy"; + static MAX_DOSE = "maxDose"; + static DM = "dm"; + static DT = "dt"; + static LIVER = "liver"; + static IY = "iy"; + static IT = "it"; + static IRA = "ira"; + static SL = "sl"; + static ELDERLY = "elderly"; + static KIDNEY = "kidney"; + static PLATELETS = "platelets"; + static RX = "rx"; + static TUBE = "tube"; + static MAX_TIME = "maxTime"; + + static getAlertTypes = (t) => { + const types = [ + { + id: DrugAlertTypeEnum.ALLERGY, + label: t(`drugAlertType.${DrugAlertTypeEnum.ALLERGY}`), + }, + { + id: DrugAlertTypeEnum.MAX_DOSE, + label: t(`drugAlertType.${DrugAlertTypeEnum.MAX_DOSE}`), + }, + { + id: DrugAlertTypeEnum.DM, + label: t(`drugAlertType.${DrugAlertTypeEnum.DM}`), + }, + { + id: DrugAlertTypeEnum.DT, + label: t(`drugAlertType.${DrugAlertTypeEnum.DT}`), + }, + { + id: DrugAlertTypeEnum.LIVER, + label: t(`drugAlertType.${DrugAlertTypeEnum.LIVER}`), + }, + { + id: DrugAlertTypeEnum.IY, + label: t(`drugAlertType.${DrugAlertTypeEnum.IY}`), + }, + { + id: DrugAlertTypeEnum.IT, + label: t(`drugAlertType.${DrugAlertTypeEnum.IT}`), + }, + + { + id: DrugAlertTypeEnum.IRA, + label: t(`drugAlertType.${DrugAlertTypeEnum.IRA}`), + }, + { + id: DrugAlertTypeEnum.SL, + label: t(`drugAlertType.${DrugAlertTypeEnum.SL}`), + }, + { + id: DrugAlertTypeEnum.ELDERLY, + label: t(`drugAlertType.${DrugAlertTypeEnum.ELDERLY}`), + }, + { + id: DrugAlertTypeEnum.KIDNEY, + label: t(`drugAlertType.${DrugAlertTypeEnum.KIDNEY}`), + }, + { + id: DrugAlertTypeEnum.PLATELETS, + label: t(`drugAlertType.${DrugAlertTypeEnum.PLATELETS}`), + }, + { + id: DrugAlertTypeEnum.RX, + label: t(`drugAlertType.${DrugAlertTypeEnum.RX}`), + }, + { + id: DrugAlertTypeEnum.TUBE, + label: t(`drugAlertType.${DrugAlertTypeEnum.TUBE}`), + }, + { + id: DrugAlertTypeEnum.MAX_TIME, + label: t(`drugAlertType.${DrugAlertTypeEnum.MAX_TIME}`), + }, + ]; + + return types.sort((a, b) => `${a?.label}`.localeCompare(`${b?.label}`)); + }; +} diff --git a/src/models/Feature.js b/src/models/Feature.js index 260b02081..a0ca70160 100644 --- a/src/models/Feature.js +++ b/src/models/Feature.js @@ -10,6 +10,7 @@ export default class Feature { static PATIENT_REVISION = "PATIENT_REVISION"; static INTERVENTION_V2 = "INTERVENTION_V2"; static AUTHORIZATION_SEGMENT = "AUTHORIZATION_SEGMENT"; + static DISABLE_ALERT_GROUPS = "DISABLE_ALERT_GROUPS"; static getFeatures(t) { return [ @@ -62,18 +63,18 @@ export default class Feature { label: t(`features.${Feature.PATIENT_REVISION}`), description: t(`featuresDescription.${Feature.PATIENT_REVISION}`), }, - { - id: Feature.INTERVENTION_V2, - label: "Intervenções V2", - description: - "A nova versão de intervenções possibilita o novo relatório de farmacoeconomia. Será liberado para todos usuários em 01/07/24", - }, { id: Feature.AUTHORIZATION_SEGMENT, label: "Autorização por Segmento", description: "Quando ativado, o usuário só conseguirá efetuar ações em segmentos onde tiver autorização. As autorizações são concedidas no cadastro do usuário.", }, + { + id: Feature.DISABLE_ALERT_GROUPS, + label: "Desabilita agrupamento de alertas", + description: + "Esta feature faz com que o agrupamento de alertas na tela de prescrição seja desativado, voltando ao modo de visualização antigo.", + }, ]; } } diff --git a/src/pages/Screening/PageHeader.jsx b/src/pages/Screening/PageHeader.jsx index 30b960d16..54269db25 100644 --- a/src/pages/Screening/PageHeader.jsx +++ b/src/pages/Screening/PageHeader.jsx @@ -1,6 +1,7 @@ import styled from "styled-components/macro"; import React, { useState, useEffect } from "react"; +import { useDispatch } from "react-redux"; import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; import { @@ -30,6 +31,9 @@ import ClinicalNotesCustomForm from "containers/Forms/ClinicalNotes/CustomForm"; import FormClinicalAlert from "containers/Forms/ClinicalAlert"; import { getErrorMessageFromException } from "utils/errorHandler"; import pageTimer from "utils/pageTimer"; +import FeatureService from "services/features"; +import SecurityService from "services/security"; +import { setCheckSummary } from "features/prescription/PrescriptionSlice"; import { ScreeningHeader } from "components/Screening/index.style"; @@ -58,10 +62,11 @@ export default function PageHeader({ checkScreening, reviewPatient, incrementClinicalNotes, - security, - featureService, userId, + roles, + features, }) { + const dispatch = useDispatch(); const params = useParams(); const id = params?.slug; const { isChecking } = prescription.check; @@ -83,6 +88,8 @@ export default function PageHeader({ delay: 150, config: config.slow, }); + const security = SecurityService(roles); + const featureService = FeatureService(features); useEffect(() => { if (!window.noharm) { @@ -157,6 +164,24 @@ export default function PageHeader({ notification.success({ message: "Número da prescrição copiado!" }); }; + const confirmCheckPrescription = (id) => { + let highRiskAlerts = []; + if ( + prescription?.content?.alertsList && + prescription.content.alertsList.length + ) { + highRiskAlerts = prescription.content.alertsList.filter( + (a) => a.level === "high" + ); + } + + if (highRiskAlerts.length > 0) { + dispatch(setCheckSummary(prescription.content)); + } else { + setPrescriptionStatus(id, "s"); + } + }; + const setPrescriptionStatus = (id, status) => { checkScreening(id, status) .then(() => { @@ -315,7 +340,7 @@ export default function PageHeader({ type="primary gtm-bt-check" icon={} ghost - onClick={() => setPrescriptionStatus(id, "s")} + onClick={() => confirmCheckPrescription(id)} loading={isChecking} style={{ marginRight: "5px" }} > diff --git a/src/services/features.js b/src/services/features.js index b08dfb702..0ee9e278a 100644 --- a/src/services/features.js +++ b/src/services/features.js @@ -41,14 +41,14 @@ const FeaturesService = (features) => { return hasFeature(Feature.PATIENT_REVISION); }; - const hasInterventionV2 = () => { - return hasFeature(Feature.INTERVENTION_V2); - }; - const hasAuthorizationSegment = () => { return hasFeature(Feature.AUTHORIZATION_SEGMENT); }; + const hasDisableAlertGroups = () => { + return hasFeature(Feature.DISABLE_ALERT_GROUPS); + }; + return { hasFeature, hasMicromedex, @@ -59,8 +59,8 @@ const FeaturesService = (features) => { hasDisableSolutionTab, hasClinicalNotesNewFormat, hasPatientRevision, - hasInterventionV2, hasAuthorizationSegment, + hasDisableAlertGroups, }; }; diff --git a/src/store/ducks/index.js b/src/store/ducks/index.js index d53ef026a..27599a86c 100644 --- a/src/store/ducks/index.js +++ b/src/store/ducks/index.js @@ -50,6 +50,7 @@ import reportPrescriptionAuditReport from "features/reports/PrescriptionAuditRep import reportEconomyReport from "features/reports/EconomyReport/EconomyReportSlice"; import reportCultureReport from "features/reports/CultureReport/CultureReportSlice"; import reportAntimicrobialHistoryReport from "features/reports/AntimicrobialHistoryReport/AntimicrobialHistoryReportSlice"; +import reportAlertListReport from "features/reports/AlertListReport/AlertListReportSlice"; const adminReducers = combineReducers({ interventionReason: adminInterventionReasonReducer, @@ -73,6 +74,7 @@ const reportReducers = combineReducers({ economy: reportEconomyReport, culture: reportCultureReport, antimicrobialHistory: reportAntimicrobialHistoryReport, + alertList: reportAlertListReport, reports: reports, }); diff --git a/src/store/ducks/prescriptions/index.js b/src/store/ducks/prescriptions/index.js index a586427e1..7c995d7e1 100644 --- a/src/store/ducks/prescriptions/index.js +++ b/src/store/ducks/prescriptions/index.js @@ -157,7 +157,6 @@ const setModalVisibility = (state = INITIAL_STATE, { modalKey, visible }) => ({ }); const removeNotes = (state = INITIAL_STATE, { id, notesType }) => { - console.log("remove notes thunk"); let listAttr; if (notesType === "allergy") { listAttr = "notesAllergies"; @@ -308,7 +307,7 @@ const checkSuccess = (state = INITIAL_STATE, { success }) => { let prescriptionStatus = success.newStatus; const headers = state.single.data.headers - ? { ...state.single.data.headers } + ? JSON.parse(JSON.stringify(state.single.data.headers)) : null; if (!isEmpty(headers)) { @@ -316,7 +315,7 @@ const checkSuccess = (state = INITIAL_STATE, { success }) => { if (headers[i.idPrescription]) { headers[i.idPrescription].status = i.status; headers[i.idPrescription].user = success.user; - headers[i.idPrescription].user = success.userId; + //headers[i.idPrescription].user = success.userId; } }); diff --git a/src/store/ducks/prescriptions/thunk.js b/src/store/ducks/prescriptions/thunk.js index 502a83560..b6160bb83 100644 --- a/src/store/ducks/prescriptions/thunk.js +++ b/src/store/ducks/prescriptions/thunk.js @@ -183,7 +183,8 @@ export const fetchScreeningThunk = }; export const checkScreeningThunk = - (idPrescription, status) => async (dispatch, getState) => { + (idPrescription, status, params = {}) => + async (dispatch, getState) => { return new Promise(async (resolve, reject) => { dispatch(prescriptionsCheckStart(idPrescription)); @@ -192,6 +193,7 @@ export const checkScreeningThunk = idPrescription, status, evaluationTime: window.noharm?.pageTimer?.getCurrentTime(), + alerts: params?.alerts, }) .catch(errorHandler); diff --git a/src/store/ducks/reset.js b/src/store/ducks/reset.js index 940c616ba..c7019cdc8 100644 --- a/src/store/ducks/reset.js +++ b/src/store/ducks/reset.js @@ -42,6 +42,7 @@ import { reset as prescriptionAuditReportReset } from "features/reports/Prescrip import { reset as economyReportReset } from "features/reports/EconomyReport/EconomyReportSlice"; import { reset as cultureReportReset } from "features/reports/CultureReport/CultureReportSlice"; import { reset as antimicrobialHistoryReportReset } from "features/reports/AntimicrobialHistoryReport/AntimicrobialHistoryReportSlice"; +import { reset as alertListReportReset } from "features/reports/AlertListReport/AlertListReportSlice"; const { clinicalNotesReset } = ClinicalNotesCreators; const { departmentsReset } = DepartmentsCreators; @@ -99,4 +100,5 @@ export const resetReduxState = (dispatch) => { dispatch(economyReportReset()); dispatch(cultureReportReset()); dispatch(antimicrobialHistoryReportReset()); + dispatch(alertListReportReset()); }; diff --git a/src/styles/Form.style.jsx b/src/styles/Form.style.jsx index 9c1579601..570b02b44 100644 --- a/src/styles/Form.style.jsx +++ b/src/styles/Form.style.jsx @@ -89,6 +89,12 @@ export const Form = styled.form` border-radius: 5px; } + .form-input-checkbox-single { + padding: 1rem; + background: #fafafa; + border-radius: 5px; + } + .form-action { display: flex; justify-content: flex-end; diff --git a/src/styles/base.css b/src/styles/base.css index 094dcf53d..ae722eee9 100644 --- a/src/styles/base.css +++ b/src/styles/base.css @@ -36,6 +36,22 @@ } } +.custom-scroll-danger { + &::-webkit-scrollbar { + -webkit-appearance: none; + width: 15px; + background-color: rgba(0, 0, 0, 0.1); + border-radius: 4px; + } + + &::-webkit-scrollbar-thumb { + border-radius: 4px; + border: 1px solid #fff; + background-color: #ffccc7; + -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); + } +} + @keyframes flickerAnimation { 0% { opacity: 0.5; diff --git a/src/translations/en.json b/src/translations/en.json index 3b1970086..a2a3784b4 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -415,7 +415,13 @@ }, "prescriptionDrugFilters": { - "alerts": "Has alerts", + "alerts": "Alerts", + "alertsAll": "Alerts: All", + "alertsHigh": "Alerts: High", + "alertsMedium": "Alerts: Medium", + "alertsLow": "Alerts: Low", + "alertsTypeGroup": "Alerts by Type", + "alertsLevelGroup": "Alerts by Risk", "diff": "Not evaluated", "am": "Antimicrobial", "hv": "High vigilance", @@ -634,11 +640,12 @@ "endEconomyDate": "ECONOMY DATE END", "economyValue": "ECONOMY VALUE", "dose": "DOSE", - "frequency": "FREQUENCIA", - "measureUnit": "UNIDADE MEDIDA", - "route": "VIA", - "prescriptionExpirationDate": "VIGENCIA", - "suspensionDate": "DATA SUSPENSAO" + "frequency": "FREQUENCY", + "measureUnit": "MEASURE UNIT", + "route": "ROUTE", + "prescriptionExpirationDate": "EXPIRE DATE", + "suspensionDate": "SUSPENSION DATE", + "observation": "OBSERVATION" }, "reportcsvCulture": { @@ -722,5 +729,30 @@ "whiteList": "No validations", "chemo": "Chemotherapy", "dialyzable": "Dialyzable" + }, + + "drugAlertType": { + "allergy": "Allergy", + "maxDose": "Max dose", + "dm": "Drug duplicity", + "dt": "Therapeutic duplicity", + "liver": "Hepatotoxic", + "iy": "Y incompatibility", + "it": "Drug interaction", + "ira": "Risk of Acute Renal Failure", + "sl": "Solution incompatibility", + "elderly": "Potentially inappropriate medication for the elderly", + "kidney": "Nephrotoxic", + "platelets": "Platelets", + "rx": "Cross reactivity", + "tube": "Probe", + "maxTime": "Treatment time", + + "exams": "Lab results x Drugs", + "duplicity": "Duplicity", + "group_pregnant_lactating": "Pregnant or Lactating", + "high": "Alerts: High", + "medium": "Alerts: Medium", + "low": "Alerts: Low" } } diff --git a/src/translations/pt.json b/src/translations/pt.json index ab1aa4b05..2827397a6 100644 --- a/src/translations/pt.json +++ b/src/translations/pt.json @@ -415,12 +415,20 @@ }, "prescriptionDrugFilters": { - "alerts": "Com alertas", + "alerts": "Alertas", + "alertsAll": "Alertas: Todos", + "alertsHigh": "Alertas: Nível Alto", + "alertsMedium": "Alertas: Nível Médio", + "alertsLow": "Alertas: Nível Baixo", + "alertsTypeGroup": "Alertas por Tipo", + "alertsLevelGroup": "Alertas por Nível", "diff": "Diferentes", "am": "Antimicrobianos", "hv": "Alta vigilância", "active": "Ativos", - "withValidation": "Com validação" + "withValidation": "Com validação", + "alertsAll_type": "Alertas: todos", + "alertsAll_level": "Alertas: todos" }, "interventionForm": { @@ -642,7 +650,8 @@ "measureUnit": "UNIDADE MEDIDA", "route": "VIA", "prescriptionExpirationDate": "VIGENCIA", - "suspensionDate": "DATA SUSPENSAO" + "suspensionDate": "DATA SUSPENSAO", + "observation": "OBSERVACAO" }, "reportcsvCulture": { @@ -728,5 +737,27 @@ "whiteList": "Sem validação", "chemo": "Quimioterápico", "dialyzable": "Dialisável" + }, + + "drugAlertType": { + "allergy": "Alergia", + "maxDose": "Dose máxima ou de alerta", + "dm": "Duplicidade Medicamentosa", + "dt": "Duplicidade Terapêutica", + "liver": "Ajuste Hepático", + "iy": "Incompatibilidade em Y", + "it": "Interação Medicamentosa", + "ira": "IRA (Risco)", + "sl": "ISL Incompatibilidade em Solução", + "elderly": "Medicamento potencialmente inapropriado para idosos", + "kidney": "Ajuste Renal", + "platelets": "Plaquetas", + "rx": "Reatividade Cruzada", + "tube": "Sonda", + "maxTime": "Tempo de tratamento", + + "high": "Alertas Nível Alto", + "medium": "Alertas Nível Médio", + "low": "Alertas Nível Baixo" } } diff --git a/src/utils/report.js b/src/utils/report.js index e4deea171..7b33ae1f2 100644 --- a/src/utils/report.js +++ b/src/utils/report.js @@ -78,6 +78,13 @@ export const filtersToDescription = (filters, filtersConfig) => { export const exportCSV = (datasource, t, namespace = "reportcsv") => { const replacer = (key, value) => (value === null ? "" : value); + const stringify = (value) => { + if (Array.isArray(value)) { + return `"${JSON.stringify(value, replacer).replaceAll('"', "")}"`; + } + + return JSON.stringify(value, replacer); + }; const header = Object.keys(datasource[0]); const headerNames = Object.keys(datasource[0]).map((k) => t(`${namespace}.${k}`) @@ -85,9 +92,7 @@ export const exportCSV = (datasource, t, namespace = "reportcsv") => { const csv = [ headerNames.join(","), ...datasource.map((row) => - header - .map((fieldName) => JSON.stringify(row[fieldName], replacer)) - .join(",") + header.map((fieldName) => stringify(row[fieldName])).join(",") ), ].join("\r\n"); diff --git a/src/utils/transformers/prescriptionDrugs.js b/src/utils/transformers/prescriptionDrugs.js index 5cf57dcc2..b21e42184 100644 --- a/src/utils/transformers/prescriptionDrugs.js +++ b/src/utils/transformers/prescriptionDrugs.js @@ -156,39 +156,76 @@ const sortPrescriptionDrugs = (items) => { .concat(whitelistItems); }; +const hasAlertLevel = (alerts, level) => { + if (alerts && alerts.length) { + return alerts.findIndex((a) => a.level === level) !== -1; + } + + return false; +}; + +const hasAlertType = (alerts, type) => { + if (alerts && alerts.length) { + return alerts.findIndex((a) => a.type === type) !== -1; + } + + return false; +}; + export const filterPrescriptionDrugs = (items, headers, filters) => { if (filters && filters.length && items) { return items.filter((i) => { - let show = true; + let show = false; - if (filters.indexOf("alerts") !== -1) { - show = show && i.alerts && i.alerts.length; + if ( + filters.indexOf("alertsAll_level") !== -1 || + filters.indexOf("alertsAll_type") !== -1 + ) { + show = show || (i.alertsComplete && i.alertsComplete.length > 0); + } + + if (filters.indexOf("alertsHigh") !== -1) { + show = show || hasAlertLevel(i.alertsComplete, "high"); + } + + if (filters.indexOf("alertsMedium") !== -1) { + show = show || hasAlertLevel(i.alertsComplete, "medium"); + } + + if (filters.indexOf("alertsLow") !== -1) { + show = show || hasAlertLevel(i.alertsComplete, "low"); } if (filters.indexOf("diff") !== -1) { - show = show && !i.checked; + show = show || !i.checked; } if (filters.indexOf("am") !== -1) { - show = show && i.am; + show = show || i.am; } if (filters.indexOf("hv") !== -1) { - show = show && i.av; + show = show || i.av; } if (filters.indexOf("withValidation") !== -1) { - show = show && !i.whiteList; + show = show || !i.whiteList; } if (filters.indexOf("active") !== -1) { - show = show && !i.suspended; + show = show || !i.suspended; - if (i.cpoe && headers[i.cpoe]) { - show = show && isActive(headers[i.cpoe]); + if (i.cpoe || headers[i.cpoe]) { + show = show || isActive(headers[i.cpoe]); } } + filters.forEach((f) => { + if (f.indexOf("drugAlertType.") !== -1) { + show = show || hasAlertType(i.alertsComplete, f.split(".")[1]); + } + }); + return show; }); } diff --git a/src/utils/transformers/prescriptions.js b/src/utils/transformers/prescriptions.js index 02e3ba13f..332d00e15 100644 --- a/src/utils/transformers/prescriptions.js +++ b/src/utils/transformers/prescriptions.js @@ -200,6 +200,31 @@ export const transformPrescription = ({ return count; }; + const alerts = []; + + if (prescription || solution || procedures) { + const allItems = [...prescription, ...solution, ...procedures]; + allItems.forEach((i) => { + if (i.alertsComplete && i.alertsComplete.length) { + const drugAlerts = i.alertsComplete.map((a, index) => ({ + ...a, + idPrescription: i.idPrescription, + cpoe: i.cpoe, // idPrescription when cpoe **refactor + drugName: i.drug, + date: item?.headers[i.idPrescription]?.date, + expire: item?.headers[i.idPrescription]?.expire, + dose: i.dose, + doseconv: i.doseconv, + measureUnit: i.measureUnit, + frequency: i.frequency, + route: i.route, + rowKey: `${a.idPrescriptionDrug}-${a.key}-${a.type}-${index}`, + })); + alerts.push(...drugAlerts); + } + }); + } + return { ...item, daysAgo, @@ -248,6 +273,7 @@ export const transformPrescription = ({ proceduresCount: countList(proceduresList), dietCount: countList(dietList), uniqueDrugs: getUniqueDrugs(prescription, solution, procedures), + alertsList: alerts, }; }; diff --git a/tests/prescription/intervention.spec.ts b/tests/prescription/intervention.spec.ts index 6dda11433..5c7677f40 100644 --- a/tests/prescription/intervention.spec.ts +++ b/tests/prescription/intervention.spec.ts @@ -8,7 +8,7 @@ test("add intervention", async ({ page }) => { .click(); await page.getByText("Paciente 99").click(); await page - .getByRole("row", { name: "Expandir linha 1 BISACODIL 5 mg" }) + .getByRole("row", { name: "Expandir linha 0 1 BISACODIL" }) .getByRole("button") .nth(1) .click(); @@ -21,8 +21,9 @@ test("add intervention", async ({ page }) => { await page.getByRole("textbox").click(); await page.getByRole("textbox").fill("teste"); await page.getByRole("button", { name: "Salvar" }).click(); + await page - .getByRole("row", { name: "Expandir linha 1 BISACODIL 5 mg" }) + .getByRole("row", { name: "Expandir linha 0 1 BISACODIL 5 mg" }) .getByRole("button") .nth(1) .click(); @@ -36,7 +37,7 @@ test("add multiple interventions and rollback", async ({ page }) => { // click intervention button await page - .getByRole("row", { name: "Expandir linha 0 ENALAPRIL 20 mg" }) + .getByRole("row", { name: "Expandir linha 0 0 ENALAPRIL" }) .getByRole("button") .nth(1) .click(); @@ -53,7 +54,7 @@ test("add multiple interventions and rollback", async ({ page }) => { // click intervention button await page - .getByRole("row", { name: "Expandir linha 0 ENALAPRIL 20 mg" }) + .getByRole("row", { name: "Expandir linha 0 0 ENALAPRIL" }) .getByRole("button") .nth(1) .click(); @@ -76,7 +77,7 @@ test("add multiple interventions and rollback", async ({ page }) => { //check created interventions await page - .getByRole("row", { name: "Expandir linha 0 ENALAPRIL 20 mg" }) + .getByRole("row", { name: "Expandir linha 0 0 ENALAPRIL" }) .getByRole("button") .nth(1) .click(); @@ -91,14 +92,14 @@ test("add multiple interventions and rollback", async ({ page }) => { await page.locator(".ant-modal-confirm-btns button").first().click(); await page - .getByRole("row", { name: "Expandir linha 0 ENALAPRIL 20 mg" }) + .getByRole("row", { name: "Expandir linha 0 0 ENALAPRIL 20 mg" }) .getByRole("button") .nth(1) .click(); await page.getByText("Pendente", { exact: true }).click(); await page.getByRole("button", { name: "rollback" }).click(); await page - .getByRole("row", { name: "Expandir linha 0 ENALAPRIL 20 mg" }) + .getByRole("row", { name: "Expandir linha 0 0 ENALAPRIL 20 mg" }) .getByRole("button") .nth(1) .click(); diff --git a/tests/prescription/interventionOutcome.spec.ts b/tests/prescription/interventionOutcome.spec.ts index f03919040..6576415cb 100644 --- a/tests/prescription/interventionOutcome.spec.ts +++ b/tests/prescription/interventionOutcome.spec.ts @@ -8,7 +8,7 @@ test("outcome: suspension", async ({ page }) => { .click(); await page.getByText("Paciente 99").click(); await page - .getByRole("row", { name: "Expandir linha 1 BISACODIL 5 mg" }) + .getByRole("row", { name: "Expandir linha 0 1 BISACODIL 5 mg" }) .getByRole("button") .nth(1) .click(); @@ -100,7 +100,7 @@ test("outcome: substitution", async ({ page }) => { .click(); await page.getByText("Paciente 99").click(); await page - .getByRole("row", { name: "Expandir linha 0 ENALAPRIL 20" }) + .getByRole("row", { name: "Expandir linha 0 0 ENALAPRIL 20" }) .getByRole("button") .nth(1) .click();