diff --git a/.github/workflows/auto-testing-label.yml b/.github/workflows/auto-testing-label.yml index 98cfd46dab3..6c6fc1002a0 100644 --- a/.github/workflows/auto-testing-label.yml +++ b/.github/workflows/auto-testing-label.yml @@ -38,7 +38,7 @@ jobs: } if (isChangesRequired) { - await github.rest.issues.createComment({ + await github.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: pr.number, diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index 1104d7f480d..0f588568bbc 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -1,6 +1,8 @@ name: Cypress Tests on: + schedule: + - cron: "30 22 * * *" pull_request: branches: - develop @@ -15,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - containers: [1, 2, 3, 4] + containers: [1, 2, 3, 4, 5, 6, 7, 8] env: REACT_CARE_API_URL: http://localhost:9000 steps: @@ -134,4 +136,4 @@ jobs: if: steps.pr_origin.outputs.is_forked == 'true' with: name: cypress-videos - path: cypress/videos \ No newline at end of file + path: cypress/videos diff --git a/.github/workflows/generate-sbom.yml b/.github/workflows/generate-sbom.yml deleted file mode 100644 index 4357fd735b1..00000000000 --- a/.github/workflows/generate-sbom.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Generate SBOM using CycloneDX - -on: - workflow_dispatch: - -jobs: - generate-sbom: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: '20' - - - name: Install dependencies - run: npm ci - - - name: Install CycloneDX NPM tool - run: npm install -g @cyclonedx/cyclonedx-npm - - - name: Generate SBOM - run: cyclonedx-npm --output-file sbom.json - - - name: Upload SBOM - uses: actions/upload-artifact@v3 - with: - name: sbom - path: sbom.json - if-no-files-found: error diff --git a/src/components/Facility/ConsultationDetails/ConsultationNursingTab.tsx b/src/components/Facility/ConsultationDetails/ConsultationNursingTab.tsx index 81078197a45..02604566869 100644 --- a/src/components/Facility/ConsultationDetails/ConsultationNursingTab.tsx +++ b/src/components/Facility/ConsultationDetails/ConsultationNursingTab.tsx @@ -4,43 +4,20 @@ import { useTranslation } from "react-i18next"; import Loading from "@/components/Common/Loading"; import PageTitle from "@/components/Common/PageTitle"; import Pagination from "@/components/Common/Pagination"; +import { ProcedureType } from "@/components/Common/prescription-builder/ProcedureBuilder"; import { ConsultationTabProps } from "@/components/Facility/ConsultationDetails/index"; -import { NursingPlot } from "@/components/Facility/Consultations/NursingPlot"; +import LogUpdateAnalayseTable from "@/components/Facility/Consultations/LogUpdateAnalayseTable"; import { + DailyRoundsRes, + NursingPlotFields, RoutineAnalysisRes, RoutineFields, } from "@/components/Facility/models"; -import { PAGINATION_LIMIT } from "@/common/constants"; +import { NURSING_CARE_PROCEDURES, PAGINATION_LIMIT } from "@/common/constants"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; -import { classNames, formatDate, formatTime } from "@/Utils/utils"; - -export default function ConsultationNursingTab(props: ConsultationTabProps) { - const { t } = useTranslation(); - return ( -
- -
-

{t("routine")}

- -
-
-

{t("nursing_care")}

- -
-
- ); -} const REVERSE_CHOICES = { appetite: { @@ -114,6 +91,116 @@ const ROUTINE_ROWS = [ { subField: true, field: "appetite" } as const, ]; +const NursingPlot = ({ consultationId }: ConsultationTabProps) => { + const { t } = useTranslation(); + const [results, setResults] = useState({}); + const [currentPage, setCurrentPage] = useState(1); + const [totalCount, setTotalCount] = useState(0); + + useEffect(() => { + const fetchDailyRounds = async ( + currentPage: number, + consultationId: string, + ) => { + const { res, data } = await request(routes.dailyRoundsAnalyse, { + body: { page: currentPage, fields: NursingPlotFields }, + pathParams: { consultationId }, + }); + if (res && res.ok && data) { + setResults(data.results); + setTotalCount(data.count); + } + }; + + fetchDailyRounds(currentPage, consultationId); + }, [consultationId, currentPage]); + + const handlePagination = (page: number) => setCurrentPage(page); + + const data = Object.entries(results).map(([date, result]) => { + if ("nursing" in result) { + return { + date: date, + nursing: result.nursing, + }; + } else { + return { + date: date, + nursing: null, + }; + } + }); + + const dataToDisplay = data + .map((x) => + x.nursing + ? x.nursing.map((f: any) => { + f["date"] = x.date; + return f; + }) + : [], + ) + .reduce((accumulator, value) => accumulator.concat(value), []); + + const filterEmpty = (field: (typeof NURSING_CARE_PROCEDURES)[number]) => { + const filtered = dataToDisplay.filter( + (i: ProcedureType) => i.procedure === field, + ); + return filtered.length > 0; + }; + + const areFieldsEmpty = () => { + let emptyFieldCount = 0; + for (const field of NURSING_CARE_PROCEDURES) { + if (!filterEmpty(field)) emptyFieldCount++; + } + return emptyFieldCount === NURSING_CARE_PROCEDURES.length; + }; + + const rows = NURSING_CARE_PROCEDURES.filter((f) => filterEmpty(f)).map( + (procedure) => ({ + field: procedure, + title: t(`NURSING_CARE_PROCEDURE__${procedure}`), + }), + ); + + const mappedData = dataToDisplay.reduce( + (acc: Record, item: any) => { + if (!acc[item.date]) acc[item.date] = {}; + acc[item.date][item.procedure] = item.description; + return acc; + }, + {}, + ); + + return ( +
+
+ {areFieldsEmpty() ? ( +
+
+ {t("no_data_found")} +
+
+ ) : ( + + )} +
+ + {totalCount > PAGINATION_LIMIT && !areFieldsEmpty() && ( +
+ +
+ )} +
+ ); +}; + const RoutineSection = ({ consultationId }: ConsultationTabProps) => { const { t } = useTranslation(); const [page, setPage] = useState(1); @@ -158,65 +245,11 @@ const RoutineSection = ({ consultationId }: ConsultationTabProps) => { return (
-
- - - - - ))} - - - - {ROUTINE_ROWS.map((row) => ( - - - {row.field && - Object.values(results).map((obj, idx) => ( - - ))} - - ))} - -
- {Object.keys(results).map((date) => ( - -

{formatDate(date)}

-

{formatTime(date)}

-
- {row.title ?? t(`LOG_UPDATE_FIELD_LABEL__${row.field!}`)} - - {(() => { - const value = obj[row.field]; - if (value == null) { - return "-"; - } - if (typeof value === "boolean") { - return t(value ? "yes" : "no"); - } - const choices = REVERSE_CHOICES[row.field]; - const choice = `${row.field.toUpperCase()}__${choices[value as keyof typeof choices]}`; - return t(choice); - })()} -
-
+ {totalCount != null && totalCount > PAGINATION_LIMIT && (
@@ -231,3 +264,24 @@ const RoutineSection = ({ consultationId }: ConsultationTabProps) => {
); }; + +export default function ConsultationNursingTab(props: ConsultationTabProps) { + const { t } = useTranslation(); + return ( +
+ +
+

{t("routine")}

+ +
+
+

{t("nursing_care")}

+ +
+
+ ); +} diff --git a/src/components/Facility/Consultations/LogUpdateAnalayseTable.tsx b/src/components/Facility/Consultations/LogUpdateAnalayseTable.tsx new file mode 100644 index 00000000000..cd8f0a32ed8 --- /dev/null +++ b/src/components/Facility/Consultations/LogUpdateAnalayseTable.tsx @@ -0,0 +1,95 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; + +import { classNames, formatDate, formatTime } from "@/Utils/utils"; + +interface SharedSectionTableProps { + data: Record; + rows: Array<{ title?: string; field?: string; subField?: boolean }>; + choices?: Record>; +} + +const LogUpdateAnalayseTable: React.FC = ({ + data, + rows, + choices = {}, +}) => { + const { t } = useTranslation(); + + // Helper function to get the display value + const getDisplayValue = ( + value: string | boolean | null | undefined, + field?: string, + ): string => { + if (value == null) { + return " "; + } + + if (typeof value === "boolean") { + return t(value ? "yes" : "no"); + } + if (field && choices[field]) { + const choice = + choices[field][value as keyof (typeof choices)[typeof field]]; + return choice ? t(`${field.toUpperCase()}__${choice}`) : "-"; + } + if (value && typeof value == "string") return value; + + return "-"; + }; + + return ( +
+ + + + + {Object.keys(data).map((date) => ( + <> + + + ))} + + + + {rows.map((row) => ( + + + {Object.values(data).map((obj, idx) => { + const value = obj[row.field!]; + return ( + + ); + })} + + ))} + +
+

{formatDate(date)}

+

{formatTime(date)}

+
+ {row.title ?? t(`LOG_UPDATE_FIELD_LABEL__${row.field!}`)} + + {getDisplayValue(value, row.field)} +
+
+ ); +}; + +export default LogUpdateAnalayseTable; diff --git a/src/components/Facility/Consultations/NursingPlot.tsx b/src/components/Facility/Consultations/NursingPlot.tsx deleted file mode 100644 index 13f5bb64201..00000000000 --- a/src/components/Facility/Consultations/NursingPlot.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import Pagination from "@/components/Common/Pagination"; -import { NursingPlotFields } from "@/components/Facility/models"; - -import { NURSING_CARE_PROCEDURES, PAGINATION_LIMIT } from "@/common/constants"; - -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import { formatDateTime } from "@/Utils/utils"; - -export const NursingPlot = ({ consultationId }: any) => { - const { t } = useTranslation(); - const [results, setResults] = useState({}); - const [currentPage, setCurrentPage] = useState(1); - const [totalCount, setTotalCount] = useState(0); - - useEffect(() => { - const fetchDailyRounds = async ( - currentPage: number, - consultationId: string, - ) => { - const { res, data } = await request(routes.dailyRoundsAnalyse, { - body: { page: currentPage, fields: NursingPlotFields }, - pathParams: { - consultationId, - }, - }); - if (res && res.ok && data) { - setResults(data.results); - setTotalCount(data.count); - } - }; - - fetchDailyRounds(currentPage, consultationId); - }, [consultationId, currentPage]); - - const handlePagination = (page: number) => { - setCurrentPage(page); - }; - - const data = Object.entries(results).map((key: any) => { - return { - date: formatDateTime(key[0]), - nursing: key[1]["nursing"], - }; - }); - - const dataToDisplay = data - .map((x) => - x.nursing.map((f: any) => { - f["date"] = x.date; - return f; - }), - ) - .reduce((accumulator, value) => accumulator.concat(value), []); - - const filterEmpty = (field: (typeof NURSING_CARE_PROCEDURES)[number]) => { - const filtered = dataToDisplay.filter((i: any) => i.procedure === field); - return filtered.length > 0; - }; - - const areFieldsEmpty = () => { - let emptyFieldCount = 0; - for (const field of NURSING_CARE_PROCEDURES) { - if (!filterEmpty(field)) emptyFieldCount++; - } - if (emptyFieldCount === NURSING_CARE_PROCEDURES.length) return true; - else return false; - }; - - return ( -
-
-
-
- {areFieldsEmpty() && ( -
-
- {t("no_data_found")} -
-
- )} - {NURSING_CARE_PROCEDURES.map( - (f) => - filterEmpty(f) && ( -
-
-
-

- {t(`NURSING_CARE_PROCEDURE__${f}`)} -

-
-
-
- {dataToDisplay - .filter((i: any) => i.procedure === f) - .map((care: any, index: number) => ( -
-
- {care.date} -
-
- {care.description} -
-
- ))} -
-
- ), - )} -
-
-
- - {!areFieldsEmpty() && totalCount > PAGINATION_LIMIT && ( -
- -
- )} -
- ); -};