diff --git a/src/app/(dashboard)/patients/actions.ts b/src/app/(dashboard)/patients/actions.ts index 15f717f..7a76fe0 100644 --- a/src/app/(dashboard)/patients/actions.ts +++ b/src/app/(dashboard)/patients/actions.ts @@ -2,8 +2,20 @@ import { fhirServer } from "@/lib/api/axios"; import { FilterFormData } from "@/model/filters"; -import format from "string-template"; import { createPatient } from "@/lib/fhir/patient"; +import { fetchLocations, generteBaseFilter } from "@/lib/api/server"; +import { createPatientFilters } from "../stats/filters"; + +export async function fetchRequiredData() { + const locations = await fetchLocations(); + const locationMap = new Map( + locations.map((location) => [location.id, location.name]) + ); + return { + locations, + locationMap, + }; +} export async function fetchData(formData: FormData) { try { @@ -11,25 +23,17 @@ export async function fetchData(formData: FormData) { formData.getAll("data")[0] as string ) as FilterFormData; - const queries: string[] = []; - - data.filters.forEach((filter) => { - const template = filter.template; - - const values: Record = {}; - - filter.params.forEach((param) => { - values[param.name] = encodeURIComponent(param.value as any); - }); - - console.log({ template, values }); + const { rawDate, baseFilter } = generteBaseFilter(data.filters); - queries.push(format(template, values)); + const query = createPatientFilters(undefined, rawDate, baseFilter, { + hasCount: false, + onlyActive: true, + formatUrl: true, }); - console.log(queries); + console.log(query); - const res = await fhirServer.get("/Patient?" + queries, {}); + const res = await fhirServer.get(query); return ( res.data.entry?.map((entry: any) => createPatient(entry.resource)) ?? [] ); diff --git a/src/app/(dashboard)/patients/content.tsx b/src/app/(dashboard)/patients/content.tsx index 554ba33..c7c6fd5 100644 --- a/src/app/(dashboard)/patients/content.tsx +++ b/src/app/(dashboard)/patients/content.tsx @@ -5,49 +5,56 @@ import { Patient } from "@/lib/fhir/types"; import Link from "next/link"; import React from "react"; -type Props = {}; +type Props = { + locationMap: Map; +}; -const Content = (props: Props) => { +const Content = ({ locationMap }: Props) => { const { data } = useGenericContext(); const patients = data as Patient[]; return ( -
+
{patients.map((patient) => (

- {patient.name} + + {patient.name} ({locationMap.get(patient.locationId) ?? "NA"}) + View{" "}

- - ART/HCC Number: {patient.identifier} - - Gender: {patient.gender} - BirthDate: {patient.birthDate} +
+ + ART/HCC Number: {patient.identifier} + + Active: {patient.active} + Gender: {patient.gender} + BirthDate: {patient.birthDate} +
{patient.phoneNumbers.map((value, index) => (
- Phone {index + 1}: {value.number} + Phone {index + 1}: {value.number} -- Owner: {value.owner} - Owner: {value.owner}
))} - Active: {patient.active} {patient.address.map((address, index) => (
- Facility: {address.facility} + Location: {address.facility} Physical: {address.physical}
))} - - Registration Date: {patient.registrationDate} - - - Registered By: {patient.registratedBy} - +
+ + Registration Date: {patient.registrationDate} + + + Registered By: {patient.registratedBy} + +
diff --git a/src/app/(dashboard)/patients/page.tsx b/src/app/(dashboard)/patients/page.tsx index 9c59c35..5d670cf 100644 --- a/src/app/(dashboard)/patients/page.tsx +++ b/src/app/(dashboard)/patients/page.tsx @@ -1,4 +1,4 @@ -import { fetchData } from "./actions"; +import { fetchData, fetchRequiredData } from "./actions"; import FilterToolbar from "../../../components/filters/toolbar"; import { patientFilters } from "@/model/filters"; import Content from "./content"; @@ -8,14 +8,16 @@ export default async function Page({ }: { searchParams: { q: string }; }) { + const data = await fetchRequiredData(); return (
- +
); diff --git a/src/app/(dashboard)/stats/actions.ts b/src/app/(dashboard)/stats/actions.ts index 79fc6dd..c87859c 100644 --- a/src/app/(dashboard)/stats/actions.ts +++ b/src/app/(dashboard)/stats/actions.ts @@ -1,7 +1,7 @@ "use server"; import { fetchBundle } from "@/lib/fhir/bundle"; -import { LocationData, SummaryItem } from "@/lib/models/types"; +import { SummaryItem } from "@/lib/models/types"; import { FilterFormData } from "@/model/filters"; import { fhirR4 } from "@smile-cdr/fhirts"; import { fixDate } from "./model"; @@ -10,16 +10,10 @@ import { createQuestionnaireResponseFilters, } from "./filters"; import { eachDayOfInterval } from "@/lib/utils"; +import { fetchLocations } from "@/lib/api/server"; export async function fetchRequiredData() { - const locationQuery = paramGenerator("/Location", { - _count: 100, - type: "https://d-tree.org/fhir/location-type|facility", - }); - var bundle = await fetchBundle([locationQuery]); - const locations = getLocationData( - bundle.entry?.[0]?.resource as fhirR4.Bundle - ); + const locations = await fetchLocations(); return { locations, }; @@ -99,9 +93,15 @@ export async function fetchData(formData: FormData) { baseFilter, false ), - createPatientFilters(["newly-diagnosed-client"], null, baseFilter, true), - createPatientFilters(["client-already-on-art"], null, baseFilter, true), - createPatientFilters(["exposed-infant"], null, baseFilter, true), + createPatientFilters(["newly-diagnosed-client"], null, baseFilter, { + hasCount: true, + }), + createPatientFilters(["client-already-on-art"], null, baseFilter, { + hasCount: true, + }), + createPatientFilters(["exposed-infant"], null, baseFilter, { + hasCount: true, + }), createQuestionnaireResponseFilters( "patient-finish-visit", rawDate, @@ -181,20 +181,6 @@ export async function fetchData(formData: FormData) { }; } -const getLocationData = (bundle: fhirR4.Bundle | undefined): LocationData[] => { - if (bundle == undefined) { - return []; - } - return ( - bundle.entry?.map((entry) => { - return { - id: entry.resource?.id ?? "", - name: (entry.resource as fhirR4.Location)?.name ?? "", - }; - }) ?? [] - ); -}; - const getResults = ( bundle: fhirR4.Bundle | undefined, summary: string[], @@ -231,12 +217,3 @@ const getResults = ( }) ?? [] ); }; - -const paramGenerator = ( - resources: string, - params: Record -) => { - return `${resources}?${Object.keys(params) - .map((key) => `${key}=${params[key]}`) - .join("&")}`; -}; diff --git a/src/app/(dashboard)/stats/filters.ts b/src/app/(dashboard)/stats/filters.ts index f40f3fb..157f450 100644 --- a/src/app/(dashboard)/stats/filters.ts +++ b/src/app/(dashboard)/stats/filters.ts @@ -1,5 +1,4 @@ -import { fhirR4 } from "@smile-cdr/fhirts"; -import { QueryParam, fixDate } from "./model"; +import { QueryParam } from "./model"; import { format } from "date-fns"; type PatientType = @@ -50,13 +49,24 @@ export const createPatientFilters = ( types: PatientType[] | undefined = undefined, date: string | string[] | null, baseFilter: Record[], - onlyActive = false + options: { + onlyActive?: boolean; + hasCount?: boolean; + formatUrl?: boolean; + } = { + hasCount: true, + onlyActive: false, + formatUrl: false, + } ) => { - const query = new QueryParam({ - _summary: "count", - }); + const query = new QueryParam({}, options.formatUrl); + + if (options.hasCount == true) { + query.add("_summary", "count"); + } + query.fromArray(baseFilter); - if (onlyActive) { + if (options.onlyActive == true) { query.add("active", true); } query.remove("date"); diff --git a/src/app/(dashboard)/stats/model.ts b/src/app/(dashboard)/stats/model.ts index 9ceba28..163e72d 100644 --- a/src/app/(dashboard)/stats/model.ts +++ b/src/app/(dashboard)/stats/model.ts @@ -5,8 +5,11 @@ export const fixDate = (date: string | string[]) => { export class QueryParam { queries: Map = new Map(); - constructor(values: Record) { + encodeUrl: boolean = false; + + constructor(values: Record, encodeUrl: boolean = false) { this.from(values); + this.encodeUrl = encodeUrl; } add(key: string, value: any) { @@ -49,9 +52,11 @@ export class QueryParam { const query = Array.from(this.queries) .map(([key, value]) => { if (key.includes("[")) { - return `${key.split("[")[0]}=${value}`; + return `${key.split("[")[0]}=${ + this.encodeUrl ? encodeURIComponent(value) : value + }`; } - return `${key}=${value}`; + return `${key}=${this.encodeUrl ? encodeURIComponent(value) : value}`; }) .join("&"); return `${resources}?${query}`; diff --git a/src/lib/api/server.ts b/src/lib/api/server.ts new file mode 100644 index 0000000..be4952f --- /dev/null +++ b/src/lib/api/server.ts @@ -0,0 +1,99 @@ +import { fetchBundle } from "../fhir/bundle"; +import { eachDayOfInterval, paramGenerator } from "../utils"; +import { fhirR4 } from "@smile-cdr/fhirts"; +import { LocationData, SummaryItem } from "@/lib/models/types"; +import { FilterFormItem } from "@/model/filters"; +import { fixDate } from "@/app/(dashboard)/stats/model"; +import format from "string-template"; + +export async function fetchLocations() { + const locationQuery = paramGenerator("/Location", { + _count: 100, + type: "https://d-tree.org/fhir/location-type|facility", + }); + var bundle = await fetchBundle([locationQuery]); + const locations = getLocationData( + bundle.entry?.[0]?.resource as fhirR4.Bundle + ); + return locations; +} + +export const generteBaseFilter = (filters: FilterFormItem[]) => { + let rawDate: string | string[] | null = null; + + const baseFilter = filters.map((filter) => { + const temp: Record = {}; + + if (filter.template == "_tag_location") { + const template = `http://smartregister.org/fhir/location-tag|${ + filter.params[0].value ?? "" + }`; + temp["_tag"] = template; + } else if (filter.template == "date") { + rawDate = + filter.params.find((e) => e.name == "date")?.value?.split("T")[0] ?? + null; + } else if (filter.template == "dateRange") { + const value = filter.params[0].value; + if (value) { + const { from, to } = value as any; + console.log(from, to); + + if (from && to) { + const start = new Date(from.split("T")[0]); + const end = new Date(to.split("T")[0]); + console.log({ start, end }); + + rawDate = eachDayOfInterval({ + start, + end, + }).map((e) => { + console.log(e); + + return e.toISOString().split("T")[0]; + }); + } else if (from) { + rawDate = [from.split("T")[0]]; + } + } + } else { + if (filter.template.includes("={")) { + const values: Record = {}; + + filter.params.forEach((param) => { + values[param.name] = encodeURIComponent(param.value as any); + }); + + const value = format(filter.template, values); + temp[value.split("=")[0]] = value.split("=")[1]; + } else { + temp[filter.template] = filter.params[0].value ?? ""; + } + } + + return temp; + }); + + if (rawDate) { + rawDate = fixDate(rawDate); + } + + return { + baseFilter, + rawDate, + }; +}; + +const getLocationData = (bundle: fhirR4.Bundle | undefined): LocationData[] => { + if (bundle == undefined) { + return []; + } + return ( + bundle.entry?.map((entry) => { + return { + id: entry.resource?.id ?? "", + name: (entry.resource as fhirR4.Location)?.name ?? "", + }; + }) ?? [] + ); +}; diff --git a/src/lib/fhir/patient.ts b/src/lib/fhir/patient.ts index f20eca3..72ff4c3 100644 --- a/src/lib/fhir/patient.ts +++ b/src/lib/fhir/patient.ts @@ -1,33 +1,36 @@ import { Patient } from "./types"; - export const createPatient = (data: any): Patient => { - return { - id: data.id, - identifier: data.identifier?.[0]?.value ?? "NA", - name: data.name[0].given[0] + " " + data.name[0].family, - firstName: data.name[0].given[0], - lastName: data.name[0].family, - gender: data.gender, - birthDate: data.birthDate, - phoneNumbers: - data.telecom?.map((value: any) => { - var array = value.value?.split("|"); - return { - number: array[1], - owner: array[2], - }; - }) ?? [], - active: data.active, - address: - data.address?.map((value: any) => ({ - facility: value.district ?? "NA", - physical: data.address?.[0]?.text ?? "NA", - })) ?? [], - registrationDate: - data.meta.tag.find( - (e: any) => e.system === "https://d-tree.org/fhir/created-on-tag" - )?.code ?? "NA", - registratedBy: data.generalPractitioner?.[0]?.display ?? "NA", - }; - }; \ No newline at end of file + return { + id: data.id, + locationId: + data.meta.tag.find( + (e: any) => e.system === "http://smartregister.org/fhir/location-tag" + )?.code ?? "NA", + identifier: data.identifier?.[0]?.value ?? "NA", + name: data.name[0].given[0] + " " + data.name[0].family, + firstName: data.name[0].given[0], + lastName: data.name[0].family, + gender: data.gender, + birthDate: data.birthDate, + phoneNumbers: + data.telecom?.map((value: any) => { + var array = value.value?.split("|"); + return { + number: array[1], + owner: array[2], + }; + }) ?? [], + active: data.active, + address: + data.address?.map((value: any) => ({ + facility: value.district ?? "NA", + physical: data.address?.[0]?.text ?? "NA", + })) ?? [], + registrationDate: + data.meta.tag.find( + (e: any) => e.system === "https://d-tree.org/fhir/created-on-tag" + )?.code ?? "NA", + registratedBy: data.generalPractitioner?.[0]?.display ?? "NA", + }; +}; diff --git a/src/lib/fhir/types.ts b/src/lib/fhir/types.ts index 420fb4e..6fc0730 100644 --- a/src/lib/fhir/types.ts +++ b/src/lib/fhir/types.ts @@ -1,5 +1,6 @@ export type Patient = { id: string, + locationId: string, identifier: string, name: string, firstName: string, diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 4701e0e..050ef94 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -34,3 +34,13 @@ export function eachDayOfInterval({ return days; } + +export const paramGenerator = ( + resources: string, + params: Record +) => { + return `${resources}?${Object.keys(params) + .map((key) => `${key}=${params[key]}`) + .join("&")}`; +}; + diff --git a/src/model/filters.ts b/src/model/filters.ts index 662a1c9..91eae08 100644 --- a/src/model/filters.ts +++ b/src/model/filters.ts @@ -23,6 +23,20 @@ export interface FilterParams { } export const patientFilters: Filter[] = [ + { + id: "filter-by-location", + name: "Search by Location", + template: "_tag_location", + isObject: true, + params: [ + { + name: "location", + title: "Enter facility", + type: FilterParamType.select, + prefillKey: "locations", + }, + ], + }, { id: "patient-search-by-first-name", name: "Search by Firstname",