From 87efdad14d9c8265e899c9c43f0e58805f8ef3c2 Mon Sep 17 00:00:00 2001 From: ElektrikSpark Date: Sat, 9 Dec 2023 18:10:32 -0600 Subject: [PATCH 1/7] allergies, conditions, immunizations, medications --- apps/expo/package.json | 1 + .../portal/(tabs)/health-record/allergies.tsx | 51 ++++++++- .../(tabs)/health-record/conditions.tsx | 52 ++++++++- .../(tabs)/health-record/immunizations.tsx | 46 +++++++- .../(tabs)/health-record/medications.tsx | 54 ++++++++- .../ui/health-record/allergy-item.tsx | 37 +++++++ .../ui/health-record/condition-item.tsx | 37 +++++++ .../ui/health-record/immunization-item.tsx | 31 ++++++ .../ui/health-record/medication-item.tsx | 40 +++++++ packages/api/src/canvas/canvas-client.ts | 22 ++-- .../api/src/router/patient-medical-history.ts | 24 +++- .../api/src/validators/allergy-intolerance.ts | 85 +++++++++++++++ packages/api/src/validators/condition.ts | 67 ++++++++++++ packages/api/src/validators/forms.ts | 0 packages/api/src/validators/immunization.ts | 48 ++++++++ .../api/src/validators/medication-request.ts | 103 ++++++++++++++++++ .../src/validators/medication-statement.ts | 59 ++++++++++ packages/api/src/validators/medication.ts | 43 ++++++++ packages/api/src/validators/test-results.ts | 0 pnpm-lock.yaml | 3 + 20 files changed, 780 insertions(+), 23 deletions(-) create mode 100644 apps/expo/src/components/ui/health-record/allergy-item.tsx create mode 100644 apps/expo/src/components/ui/health-record/condition-item.tsx create mode 100644 apps/expo/src/components/ui/health-record/immunization-item.tsx create mode 100644 apps/expo/src/components/ui/health-record/medication-item.tsx create mode 100644 packages/api/src/validators/allergy-intolerance.ts create mode 100644 packages/api/src/validators/condition.ts create mode 100644 packages/api/src/validators/forms.ts create mode 100644 packages/api/src/validators/immunization.ts create mode 100644 packages/api/src/validators/medication-request.ts create mode 100644 packages/api/src/validators/medication-statement.ts create mode 100644 packages/api/src/validators/medication.ts create mode 100644 packages/api/src/validators/test-results.ts diff --git a/apps/expo/package.json b/apps/expo/package.json index 8fc6d079..a8c5a0a8 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -32,6 +32,7 @@ "expo-splash-screen": "~0.22.0", "expo-status-bar": "~1.7.1", "expo-web-browser": "^12.5.0", + "jotai": "^2.6.0", "lucide-react-native": "^0.294.0", "nativewind": "^4.0.13", "react": "18.2.0", diff --git a/apps/expo/src/app/portal/(tabs)/health-record/allergies.tsx b/apps/expo/src/app/portal/(tabs)/health-record/allergies.tsx index 66277c82..46fee4e3 100644 --- a/apps/expo/src/app/portal/(tabs)/health-record/allergies.tsx +++ b/apps/expo/src/app/portal/(tabs)/health-record/allergies.tsx @@ -1,9 +1,56 @@ import { Text, View } from "react-native"; +import { FlashList } from "@shopify/flash-list"; +import { atom, useAtom } from "jotai"; + +import AllergyItem from "~/components/ui/health-record/allergy-item"; +import { api } from "~/utils/api"; + +export const patientAtom = atom("e7836251cbed4bd5bb2d792bc02893fd"); export default function Allergies() { + const [patientId] = useAtom(patientAtom); + + const { isLoading, isError, data, error } = + api.patientMedicalHistory.getPatientAllergies.useQuery({ patientId }); + + if (isLoading) { + return Loading...; + } + + if (isError) { + return Error: {error.message}; + } + + const allergies = data?.entry; + return ( - - Allergies + + {data.total > 0 ? ( + ( + + )} + estimatedItemSize={100} + keyExtractor={(item, index) => index.toString()} + contentContainerStyle={{ + paddingBottom: 16, + // paddingTop: 16, + // paddingHorizontal: 16, + }} + /> + ) : ( + {`No allergies found.`} + )} ); } diff --git a/apps/expo/src/app/portal/(tabs)/health-record/conditions.tsx b/apps/expo/src/app/portal/(tabs)/health-record/conditions.tsx index e9ede904..dbdf947f 100644 --- a/apps/expo/src/app/portal/(tabs)/health-record/conditions.tsx +++ b/apps/expo/src/app/portal/(tabs)/health-record/conditions.tsx @@ -1,9 +1,57 @@ import { Text, View } from "react-native"; +import { FlashList } from "@shopify/flash-list"; +import { useAtom } from "jotai"; + +import ConditionItem from "~/components/ui/health-record/condition-item"; +import { api } from "~/utils/api"; +import { patientAtom } from "./allergies"; export default function Conditions() { + const [patientId] = useAtom(patientAtom); + + const { isLoading, isError, data, error } = + api.patientMedicalHistory.getPatientConditions.useQuery({ patientId }); + + if (isLoading) { + return Loading...; + } + + if (isError) { + return Error: {error.message}; + } + + const conditions = data?.entry; + return ( - - Conditions + + {data?.total > 0 ? ( + ( + + )} + estimatedItemSize={100} + keyExtractor={(item, index) => index.toString()} + contentContainerStyle={{ + paddingBottom: 16, + // paddingTop: 16, + // paddingHorizontal: 16, + }} + /> + ) : ( + {`No immunizations found.`} + )} ); } diff --git a/apps/expo/src/app/portal/(tabs)/health-record/immunizations.tsx b/apps/expo/src/app/portal/(tabs)/health-record/immunizations.tsx index 52bdd7e1..8b9282b0 100644 --- a/apps/expo/src/app/portal/(tabs)/health-record/immunizations.tsx +++ b/apps/expo/src/app/portal/(tabs)/health-record/immunizations.tsx @@ -1,9 +1,51 @@ import { Text, View } from "react-native"; +import { FlashList } from "@shopify/flash-list"; +import { useAtom } from "jotai"; + +import ImmunizationItem from "~/components/ui/health-record/immunization-item"; +import { api } from "~/utils/api"; +import { patientAtom } from "./allergies"; export default function Immunizations() { + const [patientId] = useAtom(patientAtom); + + const { isLoading, isError, data, error } = + api.patientMedicalHistory.getPatientImmunizations.useQuery({ patientId }); + + if (isLoading) { + return Loading...; + } + + if (isError) { + return Error: {error.message}; + } + + const immunizations = data?.entry; + return ( - - Immunizations + + {data.total > 0 ? ( + ( + + )} + estimatedItemSize={100} + keyExtractor={(item, index) => index.toString()} + contentContainerStyle={{ + paddingBottom: 16, + // paddingTop: 16, + // paddingHorizontal: 16, + }} + /> + ) : ( + {`No immunizations found.`} + )} ); } diff --git a/apps/expo/src/app/portal/(tabs)/health-record/medications.tsx b/apps/expo/src/app/portal/(tabs)/health-record/medications.tsx index 35cce1d5..0800c071 100644 --- a/apps/expo/src/app/portal/(tabs)/health-record/medications.tsx +++ b/apps/expo/src/app/portal/(tabs)/health-record/medications.tsx @@ -1,9 +1,59 @@ import { Text, View } from "react-native"; +import { FlashList } from "@shopify/flash-list"; +import { useAtom } from "jotai"; + +import MedicationItem from "~/components/ui/health-record/medication-item"; +import { api } from "~/utils/api"; +import { patientAtom } from "./allergies"; export default function Medications() { + const [patientId] = useAtom(patientAtom); + + const { isLoading, isError, data, error } = + api.patientMedicalHistory.getPatientMedications.useQuery({ patientId }); + + if (isLoading) { + return Loading...; + } + + if (isError) { + return Error: {error.message}; + } + + const medications = data?.entry; + return ( - - Medications + + {data.total > 0 ? ( + ( + + )} + estimatedItemSize={100} + keyExtractor={(item, index) => index.toString()} + contentContainerStyle={{ + paddingBottom: 16, + // paddingTop: 16, + // paddingHorizontal: 16, + }} + /> + ) : ( + {`No medications found.`} + )} ); } diff --git a/apps/expo/src/components/ui/health-record/allergy-item.tsx b/apps/expo/src/components/ui/health-record/allergy-item.tsx new file mode 100644 index 00000000..c4498208 --- /dev/null +++ b/apps/expo/src/components/ui/health-record/allergy-item.tsx @@ -0,0 +1,37 @@ +import { Text, View } from "react-native"; +import { clsx } from "clsx"; + +export default function AllergyItem({ + allergen, + type, + severity, + reaction, + first, + last, +}: { + allergen: string; + type: string; + severity: string; + reaction: string; + first?: boolean; + last?: boolean; +}) { + return ( + + + {allergen} + + + {type} - {severity} - {reaction} + + + + + ); +} diff --git a/apps/expo/src/components/ui/health-record/condition-item.tsx b/apps/expo/src/components/ui/health-record/condition-item.tsx new file mode 100644 index 00000000..13e8da4d --- /dev/null +++ b/apps/expo/src/components/ui/health-record/condition-item.tsx @@ -0,0 +1,37 @@ +import { Text, View } from "react-native"; +import { clsx } from "clsx"; + +export default function ConditionItem({ + condition, + status, + onset, + abatement, + first, + last, +}: { + condition: string; + status: string; + onset: string; + abatement: string; + first?: boolean; + last?: boolean; +}) { + return ( + + + {condition} + + Status: {status} + Onset: {onset} + Abatement: {abatement} + + + + ); +} diff --git a/apps/expo/src/components/ui/health-record/immunization-item.tsx b/apps/expo/src/components/ui/health-record/immunization-item.tsx new file mode 100644 index 00000000..ce089c92 --- /dev/null +++ b/apps/expo/src/components/ui/health-record/immunization-item.tsx @@ -0,0 +1,31 @@ +import { Text, View } from "react-native"; +import { clsx } from "clsx"; + +export default function ImmunizationItem({ + immunization, + status, + first, + last, +}: { + immunization: string; + status: string; + first?: boolean; + last?: boolean; +}) { + return ( + + + {immunization} + + {status} + + + + ); +} diff --git a/apps/expo/src/components/ui/health-record/medication-item.tsx b/apps/expo/src/components/ui/health-record/medication-item.tsx new file mode 100644 index 00000000..26e1956a --- /dev/null +++ b/apps/expo/src/components/ui/health-record/medication-item.tsx @@ -0,0 +1,40 @@ +import { Text, View } from "react-native"; +import { clsx } from "clsx"; + +export default function MedicationItem({ + medication, + status, + dosage, + start, + end, + first, + last, +}: { + medication: string; + status: string; + dosage: string; + start: string; + end: string; + first?: boolean; + last?: boolean; +}) { + return ( + + + {medication} + + Status: {status} + Dosage: {dosage} + Start: {start} + End: {end} + + + + ); +} diff --git a/packages/api/src/canvas/canvas-client.ts b/packages/api/src/canvas/canvas-client.ts index d521d1d7..54818026 100644 --- a/packages/api/src/canvas/canvas-client.ts +++ b/packages/api/src/canvas/canvas-client.ts @@ -4,16 +4,18 @@ import { BundleSchema, bundleSchema, careTeamSchema, - contentSchema, documentReferenceSchema, - entryResourceSchema, - linkSchema, - resourceSchema, ResourceSchema, scheduleBundleSchema, slotBundleSchema, } from "../validators"; +import { searchAllergyIntoleranceBundleSchema } from "../validators/allergy-intolerance"; +import { searchConditionBundleSchema } from "../validators/condition"; import { searchDocumentNoticeBundleSchema } from "../validators/document-reference"; +import { searchImmunizationsBundleSchema } from "../validators/immunization"; +import { searchMedicationBundleSchema } from "../validators/medication"; +import { searchMedicationRequestBundleSchema } from "../validators/medication-request"; +import { searchMedicationStatementBundleSchema } from "../validators/medication-statement"; import { searchPaymentNoticeBundleSchema } from "../validators/payment"; export type post_GetAnOauthToken = typeof post_GetAnOauthToken; @@ -64,7 +66,7 @@ export const get_SearchAllergyintolerance = { patient: z.string().optional(), }), }), - response: z.unknown(), + response: searchAllergyIntoleranceBundleSchema, }; export type put_UpdateAllergyintolerance = typeof put_UpdateAllergyintolerance; @@ -762,7 +764,7 @@ export const get_SearchCondition = { patient: z.string().optional(), }), }), - response: z.unknown(), + response: searchConditionBundleSchema, }; export type put_UpdateCondition = typeof put_UpdateCondition; @@ -1706,7 +1708,7 @@ export const get_SearchImmunization = { patient: z.string().optional(), }), }), - response: z.unknown(), + response: searchImmunizationsBundleSchema, }; export type get_ReadLocation = typeof get_ReadLocation; @@ -1818,7 +1820,7 @@ export const get_SearchMedication = { _text: z.string().optional(), }), }), - response: z.unknown(), + response: searchMedicationBundleSchema, }; export type get_ReadMedicationrequest = typeof get_ReadMedicationrequest; @@ -1844,7 +1846,7 @@ export const get_SearchMedicationrequest = { intent: z.string().optional(), }), }), - response: z.unknown(), + response: searchMedicationRequestBundleSchema, }; export type get_SearchMedicationstatement = @@ -1857,7 +1859,7 @@ export const get_SearchMedicationstatement = { patient: z.string().optional(), }), }), - response: z.unknown(), + response: searchMedicationStatementBundleSchema, }; export type put_UpdateMedicationstatement = diff --git a/packages/api/src/router/patient-medical-history.ts b/packages/api/src/router/patient-medical-history.ts index d19c9e20..75aee28e 100644 --- a/packages/api/src/router/patient-medical-history.ts +++ b/packages/api/src/router/patient-medical-history.ts @@ -1,6 +1,12 @@ import { TRPCError } from "@trpc/server"; import { z } from "zod"; +import { + get_SearchAllergyintolerance, + get_SearchCondition, + get_SearchImmunization, + get_SearchMedicationstatement, +} from "../canvas/canvas-client"; import { createTRPCRouter, protectedCanvasProcedure } from "../trpc"; export const patientMedicalHistoryRouter = createTRPCRouter({ @@ -23,7 +29,9 @@ export const patientMedicalHistoryRouter = createTRPCRouter({ patient: patientId, }, }); - return allergiesData; + const validatedData = + get_SearchAllergyintolerance.response.parse(allergiesData); + return validatedData; } catch (error) { console.error(error); throw new TRPCError({ @@ -79,7 +87,9 @@ export const patientMedicalHistoryRouter = createTRPCRouter({ patient: patientId, }, }); - return conditionsData; + const validatedData = + get_SearchCondition.response.parse(conditionsData); + return validatedData; } catch (error) { console.error(error); throw new TRPCError({ @@ -195,7 +205,9 @@ export const patientMedicalHistoryRouter = createTRPCRouter({ patient: patientId, }, }); - return immunizationsData; + const validatedData = + get_SearchImmunization.response.parse(immunizationsData); + return validatedData; } catch (error) { console.error(error); throw new TRPCError({ @@ -218,12 +230,14 @@ export const patientMedicalHistoryRouter = createTRPCRouter({ } try { - const medicationsData = await api.get("/MedicationRequest", { + const medicationsData = await api.get("/MedicationStatement", { query: { patient: patientId, }, }); - return medicationsData; + const validatedData = + get_SearchMedicationstatement.response.parse(medicationsData); + return validatedData; } catch (error) { console.error(error); throw new TRPCError({ diff --git a/packages/api/src/validators/allergy-intolerance.ts b/packages/api/src/validators/allergy-intolerance.ts new file mode 100644 index 00000000..66be522b --- /dev/null +++ b/packages/api/src/validators/allergy-intolerance.ts @@ -0,0 +1,85 @@ +import { z } from "zod"; + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const clinicalStatusSchema = z.object({ + coding: z.array(codingSchema), + text: z.string(), +}); + +const verificationStatusSchema = z.object({ + coding: z.array(codingSchema), + text: z.string(), +}); + +const codeSchema = z.object({ + coding: z.array(codingSchema), + text: z.string(), +}); + +const reactionManifestationSchema = z.object({ + coding: z.array(codingSchema), + text: z.string().optional(), +}); + +const reactionSchema = z.object({ + manifestation: z.array(reactionManifestationSchema), + severity: z.string(), +}); + +const resourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + clinicalStatus: clinicalStatusSchema, + verificationStatus: verificationStatusSchema, + type: z.string(), + code: codeSchema, + patient: z.object({ + reference: z.string(), + }), + encounter: z + .object({ + reference: z.string(), + }) + .optional(), + onsetDateTime: z.string().optional(), + recorder: z + .object({ + reference: z.string(), + }) + .optional(), + lastOccurrence: z.string().optional(), + note: z + .array( + z.object({ + text: z.string(), + }), + ) + .optional(), + reaction: z.array(reactionSchema).optional(), +}); + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +export const searchAllergyIntoleranceBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z + .array( + z.object({ + resource: resourceSchema, + }), + ) + .optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/condition.ts b/packages/api/src/validators/condition.ts new file mode 100644 index 00000000..fd2b9d0b --- /dev/null +++ b/packages/api/src/validators/condition.ts @@ -0,0 +1,67 @@ +import { z } from "zod"; + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const statusSchema = z.object({ + coding: z.array(codingSchema), + text: z.string(), +}); + +const categorySchema = z.object({ + coding: z.array(codingSchema), + text: z.string(), +}); + +const codeSchema = z.object({ + coding: z.array(codingSchema), + text: z.string(), +}); + +const referenceSchema = z.object({ + reference: z.string(), +}); + +const noteSchema = z.object({ + text: z.string(), +}); + +const conditionResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + clinicalStatus: statusSchema.optional(), + verificationStatus: statusSchema.optional(), + category: z.array(categorySchema).optional(), + code: codeSchema.optional(), + subject: referenceSchema.optional(), + encounter: referenceSchema.optional(), + onsetDateTime: z.string().optional(), + abatementDateTime: z.string().optional(), + recordedDate: z.string().optional(), + recorder: referenceSchema.optional(), + note: z.array(noteSchema).optional(), +}); + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +export const searchConditionBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z + .array( + z.object({ + resource: conditionResourceSchema, + }), + ) + .optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/forms.ts b/packages/api/src/validators/forms.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/api/src/validators/immunization.ts b/packages/api/src/validators/immunization.ts new file mode 100644 index 00000000..6942e36d --- /dev/null +++ b/packages/api/src/validators/immunization.ts @@ -0,0 +1,48 @@ +import { z } from "zod"; + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const vaccineCodeSchema = z.object({ + coding: z.array(codingSchema), +}); + +const patientSchema = z.object({ + reference: z.string(), + type: z.string(), +}); + +// TODO - optional fields +const immunizationResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + status: z.string(), + vaccineCode: vaccineCodeSchema, + patient: patientSchema, + occurrenceDateTime: z.string(), + primarySource: z.boolean(), +}); + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +export const searchImmunizationsBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z + .array( + z.object({ + resource: immunizationResourceSchema, + }), + ) + .optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/medication-request.ts b/packages/api/src/validators/medication-request.ts new file mode 100644 index 00000000..0c6ba113 --- /dev/null +++ b/packages/api/src/validators/medication-request.ts @@ -0,0 +1,103 @@ +import { z } from "zod"; + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const medicationCodeableConceptSchema = z.object({ + coding: z.array(codingSchema), +}); + +const referenceSchema = z.object({ + reference: z.string(), + type: z.string(), +}); + +const reasonCodeSchema = z.object({ + coding: z.array(codingSchema), +}); + +const dosageQuantitySchema = z.object({ + unit: z.string().optional(), +}); + +const doseAndRateSchema = z + .object({ + doseQuantity: dosageQuantitySchema, + }) + .optional(); + +const dosageInstructionSchema = z.object({ + text: z.string(), + doseAndRate: z.array(doseAndRateSchema).optional(), +}); + +const quantitySchema = z + .object({ + value: z.number(), + }) + .optional(); + +const durationSchema = z.object({ + value: z.number(), + unit: z.string(), +}); + +const performerSchema = z + .object({ + display: z.string(), + }) + .optional(); + +const dispenseRequestSchema = z + .object({ + numberOfRepeatsAllowed: z.number().optional(), + quantity: quantitySchema, + expectedSupplyDuration: durationSchema.optional(), + performer: performerSchema, + }) + .optional(); + +const substitutionSchema = z + .object({ + allowedBoolean: z.boolean(), + }) + .optional(); + +const medicationRequestResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + status: z.string(), + intent: z.string(), + reportedBoolean: z.boolean(), + medicationCodeableConcept: medicationCodeableConceptSchema, + subject: referenceSchema, + encounter: referenceSchema, + authoredOn: z.string(), + requester: referenceSchema, + reasonCode: z.array(reasonCodeSchema), + dosageInstruction: z.array(dosageInstructionSchema), + dispenseRequest: dispenseRequestSchema, + substitution: substitutionSchema, +}); + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +export const searchMedicationRequestBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema), + entry: z.array( + z.object({ + resource: medicationRequestResourceSchema, + }), + ), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/medication-statement.ts b/packages/api/src/validators/medication-statement.ts new file mode 100644 index 00000000..cc9cf4e0 --- /dev/null +++ b/packages/api/src/validators/medication-statement.ts @@ -0,0 +1,59 @@ +import { z } from "zod"; + +const referenceSchema = z.object({ + reference: z.string(), + display: z.string().optional(), +}); + +const effectivePeriodSchema = z + .object({ + start: z.string(), + end: z.string(), + }) + .optional(); + +const dosageSchema = z + .object({ + text: z.string(), + }) + .optional(); + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const medicationCodeableConceptSchema = z.object({ + coding: z.array(codingSchema), +}); + +const medicationStatementResourceSchema = z.object({ + resourceType: z.string(), + status: z.string(), + medicationCodeableConcept: medicationCodeableConceptSchema.optional(), + medicationReference: referenceSchema.optional(), + subject: referenceSchema, + context: referenceSchema.optional(), + effectivePeriod: effectivePeriodSchema.optional(), + dosage: z.array(dosageSchema).optional(), +}); + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +export const searchMedicationStatementBundleSchema = z.object({ + resourceType: z.string(), + type: z.string(), + total: z.number(), + link: z.array(linkSchema), + entry: z.array( + z.object({ + resource: medicationStatementResourceSchema, + }), + ), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/medication.ts b/packages/api/src/validators/medication.ts new file mode 100644 index 00000000..04c74bec --- /dev/null +++ b/packages/api/src/validators/medication.ts @@ -0,0 +1,43 @@ +import { z } from "zod"; + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const textSchema = z.object({ + status: z.string(), + div: z.string(), +}); + +const codeSchema = z.object({ + coding: z.array(codingSchema), + text: z.string(), +}); + +const medicationResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + text: textSchema, + code: codeSchema, +}); + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +export const searchMedicationBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema), + entry: z.array( + z.object({ + resource: medicationResourceSchema, + }), + ), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/test-results.ts b/packages/api/src/validators/test-results.ts new file mode 100644 index 00000000..e69de29b diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93691a71..171d1c98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,6 +105,9 @@ importers: expo-web-browser: specifier: ^12.5.0 version: 12.5.0(expo@49.0.21) + jotai: + specifier: ^2.6.0 + version: 2.6.0(@types/react@18.2.39)(react@18.2.0) lucide-react-native: specifier: ^0.294.0 version: 0.294.0(prop-types@15.8.1)(react-native-svg@14.1.0)(react-native@0.72.7)(react@18.2.0) From ac540a1fa9f62d2e03714e41418a10a930917e6b Mon Sep 17 00:00:00 2001 From: ElektrikSpark Date: Sat, 9 Dec 2023 19:58:42 -0600 Subject: [PATCH 2/7] reworked validators --- .../messages/_components/create-message.tsx | 2 +- .../messages/_components/create-messages.tsx | 2 +- .../communication/create-message.tsx | 2 +- packages/api/package.json | 2 +- packages/api/src/canvas/canvas-client.ts | 44 +- packages/api/src/router/care-team.ts | 6 - packages/api/src/router/patient.ts | 6 - packages/api/src/router/practitioner.ts | 12 +- packages/api/src/validators.ts | 431 ------------------ packages/api/src/validators/care-team.ts | 55 +++ packages/api/src/validators/communication.ts | 40 ++ .../api/src/validators/document-reference.ts | 6 +- packages/api/src/validators/forms.ts | 112 +++++ packages/api/src/validators/practitioner.ts | 74 +++ packages/api/src/validators/schedule.ts | 32 ++ packages/api/src/validators/slot.ts | 26 ++ 16 files changed, 375 insertions(+), 477 deletions(-) delete mode 100644 packages/api/src/validators.ts create mode 100644 packages/api/src/validators/care-team.ts create mode 100644 packages/api/src/validators/communication.ts create mode 100644 packages/api/src/validators/practitioner.ts create mode 100644 packages/api/src/validators/schedule.ts create mode 100644 packages/api/src/validators/slot.ts diff --git a/apps/nextjs/src/app/(authenticated)/messages/_components/create-message.tsx b/apps/nextjs/src/app/(authenticated)/messages/_components/create-message.tsx index d387e161..bd352781 100644 --- a/apps/nextjs/src/app/(authenticated)/messages/_components/create-message.tsx +++ b/apps/nextjs/src/app/(authenticated)/messages/_components/create-message.tsx @@ -2,7 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; -import * as z from "zod"; +import { z } from "zod"; import { Button } from "@acme/ui/button"; import { diff --git a/apps/nextjs/src/app/(authenticated)/portal/messages/_components/create-messages.tsx b/apps/nextjs/src/app/(authenticated)/portal/messages/_components/create-messages.tsx index 8fd40015..dba2d568 100644 --- a/apps/nextjs/src/app/(authenticated)/portal/messages/_components/create-messages.tsx +++ b/apps/nextjs/src/app/(authenticated)/portal/messages/_components/create-messages.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; -import * as z from "zod"; +import { z } from "zod"; import { Button } from "@acme/ui/button"; import { diff --git a/apps/nextjs/src/components/communication/create-message.tsx b/apps/nextjs/src/components/communication/create-message.tsx index 6149be9d..bf7e3298 100644 --- a/apps/nextjs/src/components/communication/create-message.tsx +++ b/apps/nextjs/src/components/communication/create-message.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; -import * as z from "zod"; +import { z } from "zod"; import { Button } from "@acme/ui/button"; import { diff --git a/packages/api/package.json b/packages/api/package.json index 11b72df9..34c9c516 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -10,7 +10,7 @@ "lint": "eslint .", "format": "prettier --check . --ignore-path ../../.gitignore", "typecheck": "tsc --noEmit", - "client:generate": "pnpm dlx typed-openapi ./src/canvas/openapi.yaml --output ./src/canvas/canvas-client.ts --runtime zod" + "client:generate": "pnpm dlx typed-openapi ./src/canvas/openapi.yaml --output ./src/canvas/generated-client.ts --runtime zod" }, "dependencies": { "@acme/auth": "workspace:^", diff --git a/packages/api/src/canvas/canvas-client.ts b/packages/api/src/canvas/canvas-client.ts index 54818026..683235f5 100644 --- a/packages/api/src/canvas/canvas-client.ts +++ b/packages/api/src/canvas/canvas-client.ts @@ -1,22 +1,30 @@ import z from "zod"; -import { - BundleSchema, - bundleSchema, - careTeamSchema, - documentReferenceSchema, - ResourceSchema, - scheduleBundleSchema, - slotBundleSchema, -} from "../validators"; import { searchAllergyIntoleranceBundleSchema } from "../validators/allergy-intolerance"; +import { + careTeamBundleSchema, + careTeamResourceSchema, +} from "../validators/care-team"; +import { + communicationBundleSchema, + communicationResourceSchema, +} from "../validators/communication"; import { searchConditionBundleSchema } from "../validators/condition"; -import { searchDocumentNoticeBundleSchema } from "../validators/document-reference"; +import { + documentReferenceBundleSchema, + documentReferenceResourceSchema, +} from "../validators/document-reference"; import { searchImmunizationsBundleSchema } from "../validators/immunization"; import { searchMedicationBundleSchema } from "../validators/medication"; import { searchMedicationRequestBundleSchema } from "../validators/medication-request"; import { searchMedicationStatementBundleSchema } from "../validators/medication-statement"; import { searchPaymentNoticeBundleSchema } from "../validators/payment"; +import { + practitionerBundleSchema, + practitionerResourceSchema, +} from "../validators/practitioner"; +import { scheduleBundleSchema } from "../validators/schedule"; +import { slotBundleSchema } from "../validators/slot"; export type post_GetAnOauthToken = typeof post_GetAnOauthToken; export const post_GetAnOauthToken = { @@ -475,7 +483,7 @@ export const get_ReadCareteam = { care_team_id: z.string(), }), }), - response: careTeamSchema, + response: careTeamResourceSchema, }; export type put_UpdateCareteam = typeof put_UpdateCareteam; @@ -535,7 +543,7 @@ export const get_SearchCareteam = { patient: z.string().optional(), }), }), - response: z.unknown(), + response: careTeamBundleSchema, }; export type post_CreateClaim = typeof post_CreateClaim; @@ -706,7 +714,7 @@ export const get_SearchCommunicationSender = { _id: z.string().optional(), }), }), - response: BundleSchema, + response: communicationBundleSchema, }; export type post_CreateCommunication = typeof post_CreateCommunication; @@ -752,7 +760,7 @@ export const get_ReadCommunication = { communication_id: z.string(), }), }), - response: ResourceSchema, + response: communicationResourceSchema, }; export type get_SearchCondition = typeof get_SearchCondition; @@ -1488,7 +1496,7 @@ export const get_ReadDocumentreference = { document_reference_id: z.string(), }), }), - response: documentReferenceSchema, + response: documentReferenceResourceSchema, }; export type get_SearchDocumentreference = typeof get_SearchDocumentreference; @@ -1505,7 +1513,7 @@ export const get_SearchDocumentreference = { category: z.string().optional(), }), }), - response: searchDocumentNoticeBundleSchema, + response: documentReferenceBundleSchema, }; export type get_ReadEncounter = typeof get_ReadEncounter; @@ -2767,7 +2775,7 @@ export const get_ReadPractitioner = { practitioner_a_id: z.string(), }), }), - response: z.unknown(), + response: practitionerResourceSchema, }; export type get_SearchPractitioner = typeof get_SearchPractitioner; @@ -2781,7 +2789,7 @@ export const get_SearchPractitioner = { name: z.string().optional(), }), }), - response: bundleSchema, + response: practitionerBundleSchema, }; export type get_ReadProcedure = typeof get_ReadProcedure; diff --git a/packages/api/src/router/care-team.ts b/packages/api/src/router/care-team.ts index d7366530..a49f1353 100644 --- a/packages/api/src/router/care-team.ts +++ b/packages/api/src/router/care-team.ts @@ -21,12 +21,6 @@ export const careTeamRouter = createTRPCRouter({ path: { care_team_id: path.care_team_id }, }); const validatedData = get_ReadCareteam.response.parse(careTeamData); - if (validatedData.resourceType === "OperationOutcome") { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Care team not found", - }); - } return validatedData; } catch (e) { throw new TRPCError({ diff --git a/packages/api/src/router/patient.ts b/packages/api/src/router/patient.ts index 2b40c234..71fd5a1c 100644 --- a/packages/api/src/router/patient.ts +++ b/packages/api/src/router/patient.ts @@ -52,12 +52,6 @@ export const patientRouter = createTRPCRouter({ path: { patient_id: path.patient_id }, }); const validatedData = get_ReadPatient.response.parse(patientData); - if (validatedData.resourceType === "OperationOutcome") { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Patient not found", - }); - } return validatedData; } catch (error) { // Handle any other errors diff --git a/packages/api/src/router/practitioner.ts b/packages/api/src/router/practitioner.ts index 28061989..df8f2c76 100644 --- a/packages/api/src/router/practitioner.ts +++ b/packages/api/src/router/practitioner.ts @@ -22,15 +22,9 @@ export const practitionerRouter = createTRPCRouter({ name: input.query.name ?? "", }, }); - - if (practitionerData.resourceType === "OperationOutcome") { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Practitioner not found", - }); - } - - return practitionerData; + const validatedData = + get_SearchPractitioner.response.parse(practitionerData); + return validatedData; } catch (e) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", diff --git a/packages/api/src/validators.ts b/packages/api/src/validators.ts deleted file mode 100644 index f44722c9..00000000 --- a/packages/api/src/validators.ts +++ /dev/null @@ -1,431 +0,0 @@ -import * as z from "zod"; - -import type { get_ReadQuestionnaire } from "./canvas/canvas-client"; - -// Forms -export const newPatientSchema = z.object({ - name: z.string().min(2, "Name must be at least 2 characters"), - address: z.string().min(10, "Address must be at least 10 characters"), - phoneNumber: z.string().min(10, "Phone number must be at least 10 digits"), -}); -export type NewPatient = z.infer; - -// Consent -export const consentFormSchema = z.object({ - generic: z - .boolean() - .default(false) - .refine((val) => val === true, { - message: "Must grant us consent to use your health information", - }), - insurance: z - .boolean() - .default(false) - .refine((val) => val === true, { - message: "Must grant us consent to use your health insurance information", - }), -}); -export type ConsentForm = z.infer; - -// Coverage -export const coverageFormSchema = z.object({ - subscriberId: z.string().refine((value) => value.length > 0, { - message: "Can't be blank.", - }), - payorId: z.string().refine((value) => value.length > 0, { - message: "Can't be blank.", - }), -}); -export type CoverageForm = z.infer; - -// Questionnaire -export const valueCodingSchema = z.object({ - code: z.string(), - display: z.string(), - system: z.string(), -}); -export type ValueCoding = z.infer; - -export const questionItemSchema = z.object({ - linkId: z.string(), - text: z.string(), - answer: z.array( - z.object({ - valueCoding: z - .union([valueCodingSchema, z.array(valueCodingSchema)]) - .optional(), - valueString: z.string().optional(), - }), - ), -}); -export type QuestionItem = z.infer; - -export const questionnaireResponseBodySchema = z.object({ - questionnaire: z.string(), - status: z.string(), - subject: z.object({ - reference: z.string(), - type: z.string(), - }), - item: z.array(questionItemSchema), -}); -export type QuestionnaireResponseBody = z.infer< - typeof questionnaireResponseBodySchema ->; - -export function generateQuestionnaireSchema( - questionnaire: z.infer, -) { - const schemaObject: Record = {}; - - questionnaire.item?.forEach((question) => { - if (question.linkId) { - switch (question.type) { - case "choice": - if (question.repeats) { - // Define the schema for checkbox (multi-select) questions - schemaObject[question.linkId] = z - .array(valueCodingSchema) - .refine((value) => value.some((item) => item), { - message: "You have to select at least one item.", - }); - } else { - // Define the schema for radio (single-select) questions - schemaObject[question.linkId] = valueCodingSchema; - } - break; - case "text": - // Define the schema for text input questions - schemaObject[question.linkId] = z - .string() - .refine((value) => value.length > 0, { - message: "Can't be blank.", - }); - break; - default: - console.warn("Unsupported question type:", question.type); - } - } - }); - - return z.object(schemaObject); -} - -// CareTeam -export const codingSchema = z.object({ - system: z.string(), - code: z.string(), - display: z.string(), -}); - -export const roleSchema = z.object({ - coding: z.array(codingSchema), -}); - -export const memberSchema = z.object({ - reference: z.string(), - display: z.string(), -}); - -export const participantSchema = z.object({ - role: z.array(roleSchema), - member: memberSchema, -}); - -export const subjectSchema = z.object({ - reference: z.string(), - type: z.string(), - display: z.string(), -}); - -export const careTeamSchema = z.object({ - resourceType: z.string(), - id: z.string(), - status: z.string(), - name: z.string(), - subject: subjectSchema, - participant: z.array(participantSchema), -}); - -// Communication -export const LinkSchema = z.object({ - relation: z.string(), - url: z.string(), -}); - -export const RecipientSchema = z.object({ - reference: z.string(), - type: z.enum(["Patient"]), -}); - -export const SenderSchema = z.object({ - reference: z.string(), - type: z.enum(["Practitioner"]), -}); - -export const PayloadSchema = z.object({ - contentString: z.string(), -}); - -export const ResourceSchema = z.object({ - resourceType: z.enum(["Communication"]), - id: z.string(), - status: z.enum(["unknown"]), - sent: z.string(), - received: z.string(), - recipient: z.array(RecipientSchema), - sender: SenderSchema, - payload: z.array(PayloadSchema), -}); - -export const EntrySchema = z.object({ - resource: ResourceSchema, -}); - -export const BundleSchema = z.object({ - resourceType: z.enum(["Bundle"]), - type: z.enum(["searchset"]), - total: z.number(), - link: z.array(LinkSchema), - entry: z.array(EntrySchema), -}); - -// Practitioner -export const linkSchema = z.object({ - relation: z.string(), - url: z.string(), -}); - -export const identifierSchema = z.object({ - system: z.string(), - value: z.string(), -}); - -export const nameSchema = z.object({ - use: z.string(), - text: z.string(), - family: z.string(), - given: z.array(z.string()), -}); - -export const addressSchema = z.object({ - use: z.string(), - line: z.array(z.string()), - city: z.string(), - state: z.string(), - postalCode: z.string(), - country: z.string(), -}); - -export const codeSchema = z.object({ - text: z.string(), -}); - -export const periodSchema = z.object({ - start: z.string(), - end: z.string(), -}); - -export const issuerExtensionSchema = z.object({ - url: z.string(), - valueString: z.string(), -}); - -export const issuerSchema = z.object({ - extension: z.array(issuerExtensionSchema), - display: z.string(), -}); - -export const qualificationSchema = z.object({ - identifier: z.array(identifierSchema), - code: codeSchema, - period: periodSchema, - issuer: issuerSchema, -}); - -export const practitionerSchema = z.object({ - resourceType: z.string(), - id: z.string(), - identifier: z.array(identifierSchema), - name: z.array(nameSchema), - address: z.array(addressSchema), - qualification: z.array(qualificationSchema), -}); - -export const entrySchema = z.object({ - resource: practitionerSchema, -}); - -export const bundleSchema = z.object({ - resourceType: z.string(), - type: z.string(), - total: z.number(), - link: z.array(linkSchema), - entry: z.array(entrySchema), -}); - -const categorySchema = z.object({ - coding: z.array(codingSchema), -}); - -const authorSchema = z.object({ - reference: z.string(), - type: z.string(), -}); - -const custodianSchema = z.object({ - reference: z.string(), - type: z.string(), -}); - -const attachmentSchema = z.object({ - contentType: z.string(), - url: z.string(), -}); - -export const contentSchema = z.object({ - attachment: attachmentSchema, - format: codingSchema, -}); - -export const documentReferenceSchema = z.object({ - resourceType: z.literal("DocumentReference"), - id: z.string(), - status: z.literal("current"), - type: z.object({ - coding: z.array(codingSchema), - }), - category: z.array(categorySchema), - subject: subjectSchema, - date: z.string(), - author: z.array(authorSchema), - custodian: custodianSchema, - content: z.array(contentSchema), -}); - -const encounterSchema = z.object({ - reference: z.string(), - type: z.string(), -}); - -const contextSchema = z.object({ - encounter: z.array(encounterSchema), - period: periodSchema, -}); - -const documentReferenceResourceSchema = z.object({ - resourceType: z.literal("DocumentReference"), - id: z.string(), - status: z.literal("current"), - type: z.object({ - coding: z.array(codingSchema), - }), - category: z.array(categorySchema), - subject: subjectSchema, - date: z.string(), - author: z.array(authorSchema), - custodian: custodianSchema, - content: z.array(contentSchema), - context: contextSchema, -}); - -export const entryResourceSchema = z.object({ - resource: documentReferenceResourceSchema, -}); - -// -// Schedule -const scheduleTextSchema = z.object({ - status: z.string(), - div: z.string(), -}); - -const scheduleActorSchema = z.object({ - reference: z.string(), - type: z.enum(["Practitioner"]), -}); - -const scheduleResourceSchema = z.object({ - resourceType: z.enum(["Schedule"]), - id: z.string(), - text: scheduleTextSchema, - actor: z.array(scheduleActorSchema), - comment: z.string(), -}); - -const scheduleEntrySchema = z.object({ - resource: scheduleResourceSchema, -}); - -export const scheduleBundleSchema = z.object({ - resourceType: z.enum(["Bundle"]), - type: z.enum(["searchset"]), - total: z.number(), - entry: z.array(scheduleEntrySchema), -}); - -// Slot -const scheduleReferenceSchema = z.object({ - reference: z.string(), - type: z.enum(["Schedule"]), -}); - -const slotResourceSchema = z.object({ - resourceType: z.enum(["Slot"]), - schedule: scheduleReferenceSchema, - status: z.string(), - start: z.string(), - end: z.string(), -}); -export type SlotResource = z.infer; - -const slotEntrySchema = z.object({ - resource: slotResourceSchema, -}); - -export const slotBundleSchema = z.object({ - resourceType: z.enum(["Bundle"]), - type: z.enum(["searchset"]), - total: z.number(), - entry: z.array(slotEntrySchema), -}); - -// DocumentReference -export const resourceSchema = z.object({ - resourceType: z.string(), - id: z.string(), - status: z.string(), - type: z.object({ - coding: z.array(codingSchema), - }), - category: z.array( - z.object({ - coding: z.array( - z.object({ - code: z.string(), - }), - ), - }), - ), - subject: subjectSchema, - date: z.string(), - author: z.array(authorSchema), - custodian: custodianSchema, - content: z.array(contentSchema), - context: z.object({ - encounter: z.array(encounterSchema), - period: periodSchema, - }), -}); - -const documentReferenceEntrySchema = z.object({ - resource: resourceSchema, -}); - -export const documentReferenceBundleSchema = z.object({ - resourceType: z.literal("Bundle"), - type: z.string(), - total: z.number(), - // link: z.array(linkSchema).optional(), - entry: z.array(documentReferenceEntrySchema).optional(), -}); diff --git a/packages/api/src/validators/care-team.ts b/packages/api/src/validators/care-team.ts new file mode 100644 index 00000000..86452737 --- /dev/null +++ b/packages/api/src/validators/care-team.ts @@ -0,0 +1,55 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const roleSchema = z.object({ + coding: z.array(codingSchema), +}); + +const memberSchema = z.object({ + reference: z.string(), + display: z.string(), +}); + +const participantSchema = z.object({ + role: z.array(roleSchema), + member: memberSchema, +}); + +const subjectSchema = z.object({ + reference: z.string(), + type: z.string(), + display: z.string(), +}); + +export const careTeamResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + status: z.string(), + name: z.string(), + subject: subjectSchema, + participant: z.array(participantSchema), +}); + +const entrySchema = z.object({ + resource: careTeamResourceSchema, +}); + +export const careTeamBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema), + entry: z.array(entrySchema), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/communication.ts b/packages/api/src/validators/communication.ts new file mode 100644 index 00000000..03ffa053 --- /dev/null +++ b/packages/api/src/validators/communication.ts @@ -0,0 +1,40 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const referenceSchema = z.object({ + reference: z.string(), + type: z.string(), +}); + +const payloadSchema = z.object({ + contentString: z.string(), +}); + +export const communicationResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + status: z.string(), + sent: z.string(), + received: z.string(), + recipient: z.array(referenceSchema), + sender: referenceSchema, + payload: z.array(payloadSchema), +}); + +const entrySchema = z.object({ + resource: communicationResourceSchema, +}); + +export const communicationBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema), + entry: z.array(entrySchema), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/document-reference.ts b/packages/api/src/validators/document-reference.ts index ac0e1d46..f9ae2dd6 100644 --- a/packages/api/src/validators/document-reference.ts +++ b/packages/api/src/validators/document-reference.ts @@ -41,7 +41,7 @@ const contentSchema = z.object({ format: formatSchema, }); -const resourceSchema = z.object({ +export const documentReferenceResourceSchema = z.object({ resourceType: z.string().optional(), id: z.string().optional(), status: z.string().optional(), @@ -71,10 +71,10 @@ const resourceSchema = z.object({ }); const entrySchema = z.object({ - resource: resourceSchema, + resource: documentReferenceResourceSchema, }); -export const searchDocumentNoticeBundleSchema = z.object({ +export const documentReferenceBundleSchema = z.object({ resourceType: z.literal("Bundle"), type: z.string(), total: z.number(), diff --git a/packages/api/src/validators/forms.ts b/packages/api/src/validators/forms.ts index e69de29b..bfb4d200 100644 --- a/packages/api/src/validators/forms.ts +++ b/packages/api/src/validators/forms.ts @@ -0,0 +1,112 @@ +import { z } from "zod"; + +import type { get_ReadQuestionnaire } from "../canvas/canvas-client"; + +// Intake Forms +export const newPatientSchema = z.object({ + name: z.string().min(2, "Name must be at least 2 characters"), + address: z.string().min(10, "Address must be at least 10 characters"), + phoneNumber: z.string().min(10, "Phone number must be at least 10 digits"), +}); +export type NewPatient = z.infer; + +// Consent +export const consentFormSchema = z.object({ + generic: z + .boolean() + .default(false) + .refine((val) => val === true, { + message: "Must grant us consent to use your health information", + }), + insurance: z + .boolean() + .default(false) + .refine((val) => val === true, { + message: "Must grant us consent to use your health insurance information", + }), +}); +export type ConsentForm = z.infer; + +// Coverage +export const coverageFormSchema = z.object({ + subscriberId: z.string().refine((value) => value.length > 0, { + message: "Can't be blank.", + }), + payorId: z.string().refine((value) => value.length > 0, { + message: "Can't be blank.", + }), +}); +export type CoverageForm = z.infer; + +// Questionnaires +export const valueCodingSchema = z.object({ + code: z.string(), + display: z.string(), + system: z.string(), +}); +export type ValueCoding = z.infer; + +export const questionItemSchema = z.object({ + linkId: z.string(), + text: z.string(), + answer: z.array( + z.object({ + valueCoding: z + .union([valueCodingSchema, z.array(valueCodingSchema)]) + .optional(), + valueString: z.string().optional(), + }), + ), +}); +export type QuestionItem = z.infer; + +export const questionnaireResponseBodySchema = z.object({ + questionnaire: z.string(), + status: z.string(), + subject: z.object({ + reference: z.string(), + type: z.string(), + }), + item: z.array(questionItemSchema), +}); +export type QuestionnaireResponseBody = z.infer< + typeof questionnaireResponseBodySchema +>; + +export function generateQuestionnaireSchema( + questionnaire: z.infer, +) { + const schemaObject: Record = {}; + + questionnaire.item?.forEach((question) => { + if (question.linkId) { + switch (question.type) { + case "choice": + if (question.repeats) { + // Define the schema for checkbox (multi-select) questions + schemaObject[question.linkId] = z + .array(valueCodingSchema) + .refine((value) => value.some((item) => item), { + message: "You have to select at least one item.", + }); + } else { + // Define the schema for radio (single-select) questions + schemaObject[question.linkId] = valueCodingSchema; + } + break; + case "text": + // Define the schema for text input questions + schemaObject[question.linkId] = z + .string() + .refine((value) => value.length > 0, { + message: "Can't be blank.", + }); + break; + default: + console.warn("Unsupported question type:", question.type); + } + } + }); + + return z.object(schemaObject); +} diff --git a/packages/api/src/validators/practitioner.ts b/packages/api/src/validators/practitioner.ts new file mode 100644 index 00000000..7f3d2e1a --- /dev/null +++ b/packages/api/src/validators/practitioner.ts @@ -0,0 +1,74 @@ +import { z } from "zod"; + +const identifierSchema = z.object({ + system: z.string(), + value: z.string(), +}); + +const nameSchema = z.object({ + use: z.string(), + text: z.string(), + family: z.string(), + given: z.array(z.string()), +}); + +const addressSchema = z.object({ + use: z.string(), + line: z.array(z.string()), + city: z.string(), + state: z.string(), + postalCode: z.string(), + country: z.string(), +}); + +const periodSchema = z.object({ + start: z.string(), + end: z.string(), +}); + +const extensionSchema = z.object({ + url: z.string(), + valueString: z.string(), +}); + +const issuerSchema = z.object({ + extension: z.array(extensionSchema).optional(), + display: z.string(), +}); + +const qualificationSchema = z.object({ + identifier: z.array(identifierSchema), + code: z.object({ + text: z.string(), + }), + period: periodSchema, + issuer: issuerSchema, +}); + +export const practitionerResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + identifier: z.array(identifierSchema), + name: z.array(nameSchema), + address: z.array(addressSchema), + qualification: z.array(qualificationSchema), +}); + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +export const practitionerBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema), + entry: z.array( + z.object({ + resource: practitionerResourceSchema, + }), + ), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/schedule.ts b/packages/api/src/validators/schedule.ts new file mode 100644 index 00000000..4c9d0a9b --- /dev/null +++ b/packages/api/src/validators/schedule.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; + +const textSchema = z.object({ + status: z.string(), + div: z.string(), +}); + +const actorSchema = z.object({ + reference: z.string(), + type: z.string(), +}); + +const scheduleResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + text: textSchema, + actor: z.array(actorSchema), + comment: z.string(), +}); + +const entrySchema = z.object({ + resource: scheduleResourceSchema, +}); + +export const scheduleBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + entry: z.array(entrySchema), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/slot.ts b/packages/api/src/validators/slot.ts new file mode 100644 index 00000000..a6a670ad --- /dev/null +++ b/packages/api/src/validators/slot.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; + +const scheduleReferenceSchema = z.object({ + reference: z.string(), + type: z.enum(["Schedule"]), +}); + +const slotResourceSchema = z.object({ + resourceType: z.enum(["Slot"]), + schedule: scheduleReferenceSchema, + status: z.string(), + start: z.string(), + end: z.string(), +}); +export type SlotResource = z.infer; + +const slotEntrySchema = z.object({ + resource: slotResourceSchema, +}); + +export const slotBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + entry: z.array(slotEntrySchema), +}); From 74f0d7e66767986104f18f47af2b2626d67dcb88 Mon Sep 17 00:00:00 2001 From: ElektrikSpark Date: Sun, 10 Dec 2023 03:03:15 -0600 Subject: [PATCH 3/7] still reworking validation --- packages/api/src/canvas/canvas-client.ts | 16 +- packages/api/src/validators/allergen.ts | 40 +++++ packages/api/src/validators/appointment.ts | 78 ++++++++++ packages/api/src/validators/care-team.ts | 4 +- packages/api/src/validators/communication.ts | 4 +- packages/api/src/validators/consent.ts | 63 ++++++++ packages/api/src/validators/coverage.ts | 55 +++++++ .../api/src/validators/diagnostic-report.ts | 51 +++++++ .../api/src/validators/document-reference.ts | 4 +- packages/api/src/validators/encounter.ts | 85 +++++++++++ packages/api/src/validators/goal.ts | 63 ++++++++ .../api/src/validators/medication-request.ts | 14 +- .../src/validators/medication-statement.ts | 14 +- packages/api/src/validators/medication.ts | 14 +- packages/api/src/validators/observation.ts | 69 +++++++++ packages/api/src/validators/patient.ts | 144 ++++++++++++++++++ packages/api/src/validators/payment.ts | 14 +- packages/api/src/validators/practitioner.ts | 14 +- .../src/validators/questionnaire-response.ts | 60 ++++++++ packages/api/src/validators/questionnaire.ts | 55 +++++++ packages/api/src/validators/schedule.ts | 2 +- packages/api/src/validators/slot.ts | 2 +- packages/api/src/validators/task.ts | 74 +++++++++ 23 files changed, 896 insertions(+), 43 deletions(-) create mode 100644 packages/api/src/validators/allergen.ts create mode 100644 packages/api/src/validators/appointment.ts create mode 100644 packages/api/src/validators/consent.ts create mode 100644 packages/api/src/validators/coverage.ts create mode 100644 packages/api/src/validators/diagnostic-report.ts create mode 100644 packages/api/src/validators/encounter.ts create mode 100644 packages/api/src/validators/goal.ts create mode 100644 packages/api/src/validators/observation.ts create mode 100644 packages/api/src/validators/patient.ts create mode 100644 packages/api/src/validators/questionnaire-response.ts create mode 100644 packages/api/src/validators/questionnaire.ts create mode 100644 packages/api/src/validators/task.ts diff --git a/packages/api/src/canvas/canvas-client.ts b/packages/api/src/canvas/canvas-client.ts index 683235f5..03dc1d3b 100644 --- a/packages/api/src/canvas/canvas-client.ts +++ b/packages/api/src/canvas/canvas-client.ts @@ -10,14 +10,20 @@ import { communicationResourceSchema, } from "../validators/communication"; import { searchConditionBundleSchema } from "../validators/condition"; +import { coverageBundleSchema } from "../validators/coverage"; import { documentReferenceBundleSchema, documentReferenceResourceSchema, } from "../validators/document-reference"; +import { goalBundleSchema, goalResourceSchema } from "../validators/goal"; import { searchImmunizationsBundleSchema } from "../validators/immunization"; import { searchMedicationBundleSchema } from "../validators/medication"; import { searchMedicationRequestBundleSchema } from "../validators/medication-request"; import { searchMedicationStatementBundleSchema } from "../validators/medication-statement"; +import { + observationBundleSchema, + observationResourceSchema, +} from "../validators/observation"; import { searchPaymentNoticeBundleSchema } from "../validators/payment"; import { practitionerBundleSchema, @@ -1191,7 +1197,7 @@ export const get_SearchCoverage = { _offset: z.string().optional(), }), }), - response: z.unknown(), + response: coverageBundleSchema, }; export type post_CreateCoverage = typeof post_CreateCoverage; @@ -1551,7 +1557,7 @@ export const get_ReadGoal = { goal_id: z.string(), }), }), - response: z.unknown(), + response: goalResourceSchema, }; export type get_SearchGoal = typeof get_SearchGoal; @@ -1563,7 +1569,7 @@ export const get_SearchGoal = { patient: z.string().optional(), }), }), - response: z.unknown(), + response: goalBundleSchema, }; export type get_SearchGroup = typeof get_SearchGroup; @@ -1983,7 +1989,7 @@ export const get_SearchObservation = { }), body: z.unknown(), }), - response: z.unknown(), + response: observationBundleSchema, }; export type post_CreateObservationWComponents = @@ -2049,7 +2055,7 @@ export const get_ReadObservation = { observation_id: z.string(), }), }), - response: z.unknown(), + response: observationResourceSchema, }; export type get_ReadOrganization = typeof get_ReadOrganization; diff --git a/packages/api/src/validators/allergen.ts b/packages/api/src/validators/allergen.ts new file mode 100644 index 00000000..b1535f74 --- /dev/null +++ b/packages/api/src/validators/allergen.ts @@ -0,0 +1,40 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string().optional(), +}); + +const textSchema = z.object({ + status: z.string(), + div: z.string(), +}); + +export const allergenResourceSchema = z.object({ + resourceType: z.literal("Allergen"), + id: z.string(), + text: textSchema, + code: z.object({ + coding: z.array(codingSchema), + }), +}); + +const entrySchema = z.object({ + resource: allergenResourceSchema, +}); + +export const allergenBundleSchema = z.object({ + resourceType: z.literal("Bundle"), + type: z.literal("searchset"), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/appointment.ts b/packages/api/src/validators/appointment.ts new file mode 100644 index 00000000..51126527 --- /dev/null +++ b/packages/api/src/validators/appointment.ts @@ -0,0 +1,78 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const codingSchema = z.object({ + system: z.string().optional(), + code: z.string(), + display: z.string().optional(), + userSelected: z.boolean().optional(), +}); + +const endpointSchema = z.object({ + resourceType: z.literal("Endpoint"), + id: z.string(), + status: z.string(), + connectionType: z.object({ + code: z.string(), + }), + payloadType: z.array( + z.object({ + coding: z.array(codingSchema), + }), + ), + address: z.string(), +}); + +const appointmentTypeSchema = z.object({ + coding: z.array(codingSchema), +}); + +const reasonCodeSchema = z.object({ + coding: z.array(codingSchema), + text: z.string().optional(), +}); + +const supportingInformationSchema = z.object({ + reference: z.string(), + type: z.string(), +}); + +const participantSchema = z.object({ + actor: z.object({ + reference: z.string(), + type: z.string(), + }), + status: z.string(), +}); + +export const appointmentResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + contained: z.array(endpointSchema).optional(), + status: z.string(), + appointmentType: appointmentTypeSchema.optional(), + reasonCode: z.array(reasonCodeSchema).optional(), + description: z.string().optional(), + supportingInformation: z.array(supportingInformationSchema).optional(), + start: z.string(), + end: z.string(), + participant: z.array(participantSchema), +}); + +const entrySchema = z.object({ + resource: appointmentResourceSchema, +}); + +export const appointmentBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/care-team.ts b/packages/api/src/validators/care-team.ts index 86452737..972c29da 100644 --- a/packages/api/src/validators/care-team.ts +++ b/packages/api/src/validators/care-team.ts @@ -48,8 +48,8 @@ export const careTeamBundleSchema = z.object({ resourceType: z.enum(["Bundle"]), type: z.enum(["searchset"]), total: z.number(), - link: z.array(linkSchema), - entry: z.array(entrySchema), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), }); // Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/communication.ts b/packages/api/src/validators/communication.ts index 03ffa053..3d9b0b6b 100644 --- a/packages/api/src/validators/communication.ts +++ b/packages/api/src/validators/communication.ts @@ -33,8 +33,8 @@ export const communicationBundleSchema = z.object({ resourceType: z.enum(["Bundle"]), type: z.enum(["searchset"]), total: z.number(), - link: z.array(linkSchema), - entry: z.array(entrySchema), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), }); // Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/consent.ts b/packages/api/src/validators/consent.ts new file mode 100644 index 00000000..a64af725 --- /dev/null +++ b/packages/api/src/validators/consent.ts @@ -0,0 +1,63 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const codingSchema = z.object({ + system: z.string(), + display: z.string(), +}); + +const categorySchema = z.object({ + coding: z.array(codingSchema), +}); + +const referenceSchema = z.object({ + reference: z.string(), + type: z.string(), +}); + +const periodSchema = z.object({ + start: z.string(), + end: z.string().optional(), +}); + +const provisionSchema = z.object({ + period: periodSchema.optional(), +}); + +const scopeSchema = z.object({ + text: z.string(), +}); + +const sourceAttachmentSchema = z.object({ + url: z.string(), +}); + +const consentResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + status: z.string(), + scope: scopeSchema, + category: z.array(categorySchema), + patient: referenceSchema, + dateTime: z.string(), + sourceAttachment: sourceAttachmentSchema.optional(), + provision: provisionSchema.optional(), +}); + +const entrySchema = z.object({ + resource: consentResourceSchema, +}); + +export const consentBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/coverage.ts b/packages/api/src/validators/coverage.ts new file mode 100644 index 00000000..6302317a --- /dev/null +++ b/packages/api/src/validators/coverage.ts @@ -0,0 +1,55 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const referenceSchema = z.object({ + reference: z.string(), + type: z.string(), + display: z.string().optional(), +}); + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const relationshipSchema = z.object({ + coding: z.array(codingSchema), + text: z.string(), +}); + +const periodSchema = z.object({ + start: z.string(), + // Include 'end' if it can be part of the response +}); + +const coverageResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + status: z.string(), + subscriber: referenceSchema, + subscriberId: z.string(), + beneficiary: referenceSchema, + relationship: relationshipSchema, + period: periodSchema, + payor: z.array(referenceSchema), + order: z.number(), +}); + +const entrySchema = z.object({ + resource: coverageResourceSchema, +}); + +export const coverageBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/diagnostic-report.ts b/packages/api/src/validators/diagnostic-report.ts new file mode 100644 index 00000000..e19368f7 --- /dev/null +++ b/packages/api/src/validators/diagnostic-report.ts @@ -0,0 +1,51 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const categorySchema = z.object({ + coding: z.array(codingSchema), +}); + +const codeSchema = z.object({ + coding: z.array(codingSchema), +}); + +const referenceSchema = z.object({ + reference: z.string(), + type: z.string(), +}); + +export const diagnosticReportResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + status: z.string(), + category: z.array(categorySchema).optional(), + code: codeSchema, + subject: referenceSchema, + effectiveDateTime: z.string(), + issued: z.string(), + performer: z.array(referenceSchema).optional(), +}); + +const entrySchema = z.object({ + resource: diagnosticReportResourceSchema, +}); + +export const diagnosticReportBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/document-reference.ts b/packages/api/src/validators/document-reference.ts index f9ae2dd6..dd5a9e41 100644 --- a/packages/api/src/validators/document-reference.ts +++ b/packages/api/src/validators/document-reference.ts @@ -78,6 +78,6 @@ export const documentReferenceBundleSchema = z.object({ resourceType: z.literal("Bundle"), type: z.string(), total: z.number(), - link: z.array(linkSchema), - entry: z.array(entrySchema), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), }); diff --git a/packages/api/src/validators/encounter.ts b/packages/api/src/validators/encounter.ts new file mode 100644 index 00000000..93d1b817 --- /dev/null +++ b/packages/api/src/validators/encounter.ts @@ -0,0 +1,85 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const identifierSchema = z.object({ + id: z.string().optional(), + system: z.string().optional(), + value: z.string().optional(), +}); + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const typeSchema = z.object({ + coding: z.array(codingSchema), +}); + +const referenceSchema = z.object({ + reference: z.string(), + type: z.string(), + display: z.string().optional(), +}); + +const periodSchema = z.object({ + start: z.string(), + end: z.string().optional(), +}); + +const participantSchema = z.object({ + type: z.array(typeSchema).optional(), + period: periodSchema.optional(), + individual: referenceSchema.optional(), +}); + +const dischargeDispositionSchema = z.object({ + coding: z.array(codingSchema), +}); + +const hospitalizationSchema = z.object({ + dischargeDisposition: dischargeDispositionSchema.optional(), +}); + +const locationSchema = z.object({ + location: referenceSchema.optional(), +}); + +export const encounterResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + identifier: z.array(identifierSchema).optional(), + status: z.string(), + class: z + .object({ + system: z.string().optional(), + }) + .optional(), + type: z.array(typeSchema).optional(), + subject: referenceSchema, + participant: z.array(participantSchema).optional(), + period: periodSchema, + reasonCode: z.array(typeSchema).optional(), + reasonReference: z.array(referenceSchema).optional(), + hospitalization: hospitalizationSchema.optional(), + location: z.array(locationSchema).optional(), +}); + +const entrySchema = z.object({ + resource: encounterResourceSchema, +}); + +export const encounterBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/goal.ts b/packages/api/src/validators/goal.ts new file mode 100644 index 00000000..783f8b80 --- /dev/null +++ b/packages/api/src/validators/goal.ts @@ -0,0 +1,63 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const achievementStatusSchema = z.object({ + coding: z.array(codingSchema), +}); + +const prioritySchema = z.object({ + coding: z.array(codingSchema), +}); + +const descriptionSchema = z.object({ + text: z.string(), +}); + +const referenceSchema = z.object({ + reference: z.string(), + type: z.string(), +}); + +const noteSchema = z.object({ + id: z.string().optional(), + authorReference: referenceSchema.optional(), + time: z.string().optional(), + text: z.string().optional(), +}); + +export const goalResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + lifecycleStatus: z.string(), + achievementStatus: achievementStatusSchema, + priority: prioritySchema.optional(), + description: descriptionSchema, + subject: referenceSchema, + startDate: z.string(), + expressedBy: referenceSchema.optional(), + note: z.array(noteSchema).optional(), +}); + +const entrySchema = z.object({ + resource: goalResourceSchema, +}); + +export const goalBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/medication-request.ts b/packages/api/src/validators/medication-request.ts index 0c6ba113..3afdf751 100644 --- a/packages/api/src/validators/medication-request.ts +++ b/packages/api/src/validators/medication-request.ts @@ -92,12 +92,14 @@ export const searchMedicationRequestBundleSchema = z.object({ resourceType: z.enum(["Bundle"]), type: z.enum(["searchset"]), total: z.number(), - link: z.array(linkSchema), - entry: z.array( - z.object({ - resource: medicationRequestResourceSchema, - }), - ), + link: z.array(linkSchema).optional(), + entry: z + .array( + z.object({ + resource: medicationRequestResourceSchema, + }), + ) + .optional(), }); // Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/medication-statement.ts b/packages/api/src/validators/medication-statement.ts index cc9cf4e0..53a607fb 100644 --- a/packages/api/src/validators/medication-statement.ts +++ b/packages/api/src/validators/medication-statement.ts @@ -48,12 +48,14 @@ export const searchMedicationStatementBundleSchema = z.object({ resourceType: z.string(), type: z.string(), total: z.number(), - link: z.array(linkSchema), - entry: z.array( - z.object({ - resource: medicationStatementResourceSchema, - }), - ), + link: z.array(linkSchema).optional(), + entry: z + .array( + z.object({ + resource: medicationStatementResourceSchema, + }), + ) + .optional(), }); // Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/medication.ts b/packages/api/src/validators/medication.ts index 04c74bec..6da6c59e 100644 --- a/packages/api/src/validators/medication.ts +++ b/packages/api/src/validators/medication.ts @@ -32,12 +32,14 @@ export const searchMedicationBundleSchema = z.object({ resourceType: z.enum(["Bundle"]), type: z.enum(["searchset"]), total: z.number(), - link: z.array(linkSchema), - entry: z.array( - z.object({ - resource: medicationResourceSchema, - }), - ), + link: z.array(linkSchema).optional(), + entry: z + .array( + z.object({ + resource: medicationResourceSchema, + }), + ) + .optional(), }); // Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/observation.ts b/packages/api/src/validators/observation.ts new file mode 100644 index 00000000..050f47b3 --- /dev/null +++ b/packages/api/src/validators/observation.ts @@ -0,0 +1,69 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const categorySchema = z.object({ + coding: z.array(codingSchema), +}); + +const codeSchema = z.object({ + coding: z.array(codingSchema), +}); + +const referenceSchema = z.object({ + reference: z.string(), + type: z.string().optional(), +}); + +const valueQuantitySchema = z.object({ + value: z.number(), + unit: z.string().optional(), +}); + +const componentSchema = z.object({ + code: codeSchema, + valueQuantity: valueQuantitySchema.optional(), +}); + +export const observationResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + status: z.string(), + category: z.array(categorySchema).optional(), + code: codeSchema, + subject: referenceSchema, + effectiveDateTime: z.string().optional(), + issued: z.string().optional(), + dataAbsentReason: z + .object({ + coding: z.array(codingSchema), + }) + .optional(), + hasMember: z.array(referenceSchema).optional(), + component: z.array(componentSchema).optional(), + derivedFrom: z.array(referenceSchema).optional(), + valueQuantity: valueQuantitySchema.optional(), +}); + +const entrySchema = z.object({ + resource: observationResourceSchema, +}); + +export const observationBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/patient.ts b/packages/api/src/validators/patient.ts new file mode 100644 index 00000000..1f87a85a --- /dev/null +++ b/packages/api/src/validators/patient.ts @@ -0,0 +1,144 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const textSchema = z.object({ + status: z.string(), + div: z.string(), +}); + +const extensionSchema = z + .object({ + url: z.string(), + // The value could be different types; you may need to adjust based on your data + valueCode: z.string().optional(), + valueCodeableConcept: z + .object({ + coding: z.array(codingSchema).optional(), + text: z.string().optional(), + }) + .optional(), + valueString: z.string().optional(), + valueBoolean: z.boolean().optional(), + valueIdentifier: z + .object({ + system: z.string().optional(), + value: z.string().optional(), + }) + .optional(), + }) + .optional(); + +const identifierSchema = z.object({ + use: z.string().optional(), + type: z + .object({ + coding: z.array(codingSchema), + }) + .optional(), + system: z.string().optional(), + value: z.string(), + assigner: z + .object({ + display: z.string(), + }) + .optional(), + period: z + .object({ + start: z.string().optional(), + end: z.string().optional(), + }) + .optional(), +}); + +const nameSchema = z.object({ + use: z.string().optional(), + family: z.string().optional(), + given: z.array(z.string()).optional(), + period: z + .object({ + start: z.string().optional(), + end: z.string().optional(), + }) + .optional(), +}); + +const telecomSchema = z.object({ + system: z.string(), + value: z.string(), + use: z.string().optional(), + rank: z.number().optional(), +}); + +const addressSchema = z.object({ + use: z.string().optional(), + type: z.string().optional(), + line: z.array(z.string()).optional(), + city: z.string().optional(), + state: z.string().optional(), + postalCode: z.string().optional(), + country: z.string().optional(), +}); + +const contactSchema = z.object({ + relationship: z.array( + z.object({ + coding: z.array(codingSchema), + text: z.string().optional(), + }), + ), + name: z + .object({ + text: z.string(), + }) + .optional(), + telecom: z.array(telecomSchema).optional(), +}); + +const communicationSchema = z.object({ + language: z.object({ + coding: z.array(codingSchema), + text: z.string().optional(), + }), +}); + +// TODO - check with generated code +const patientResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + text: textSchema, + extension: z.array(extensionSchema).optional(), + identifier: z.array(identifierSchema).optional(), + active: z.boolean().optional(), + name: z.array(nameSchema).optional(), + telecom: z.array(telecomSchema).optional(), + gender: z.string().optional(), + birthDate: z.string().optional(), + deceasedBoolean: z.boolean().optional(), + address: z.array(addressSchema).optional(), + contact: z.array(contactSchema).optional(), + communication: z.array(communicationSchema).optional(), +}); + +const entrySchema = z.object({ + resource: patientResourceSchema, +}); + +export const patientBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/payment.ts b/packages/api/src/validators/payment.ts index e7f8a715..72eff2ff 100644 --- a/packages/api/src/validators/payment.ts +++ b/packages/api/src/validators/payment.ts @@ -42,12 +42,14 @@ export const searchPaymentNoticeBundleSchema = z.object({ resourceType: z.literal("Bundle"), type: z.string(), total: z.number(), - link: z.array(linkSchema), - entry: z.array( - z.object({ - resource: resourceSchema, - }), - ), + link: z.array(linkSchema).optional(), + entry: z + .array( + z.object({ + resource: resourceSchema, + }), + ) + .optional(), }); // Usage example diff --git a/packages/api/src/validators/practitioner.ts b/packages/api/src/validators/practitioner.ts index 7f3d2e1a..0a99c4d6 100644 --- a/packages/api/src/validators/practitioner.ts +++ b/packages/api/src/validators/practitioner.ts @@ -63,12 +63,14 @@ export const practitionerBundleSchema = z.object({ resourceType: z.enum(["Bundle"]), type: z.enum(["searchset"]), total: z.number(), - link: z.array(linkSchema), - entry: z.array( - z.object({ - resource: practitionerResourceSchema, - }), - ), + link: z.array(linkSchema).optional(), + entry: z + .array( + z.object({ + resource: practitionerResourceSchema, + }), + ) + .optional(), }); // Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/questionnaire-response.ts b/packages/api/src/validators/questionnaire-response.ts new file mode 100644 index 00000000..a8d5f72c --- /dev/null +++ b/packages/api/src/validators/questionnaire-response.ts @@ -0,0 +1,60 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const extensionSchema = z.object({ + url: z.string(), + valueString: z.string().optional(), +}); + +const referenceSchema = z.object({ + reference: z.string(), + type: z.string(), +}); + +const valueCodingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const answerSchema = z.object({ + valueCoding: valueCodingSchema.optional(), + valueString: z.string().optional(), +}); + +const itemSchema = z.object({ + linkId: z.string(), + text: z.string(), + answer: z.array(answerSchema).optional(), +}); + +const questionnaireResponseResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + extension: z.array(extensionSchema).optional(), + questionnaire: z.string(), + status: z.string(), + subject: referenceSchema, + encounter: referenceSchema.optional(), + authored: z.string(), + author: referenceSchema.optional(), + item: z.array(itemSchema).optional(), +}); + +const entrySchema = z.object({ + resource: questionnaireResponseResourceSchema, +}); + +export const questionnaireResponseBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/questionnaire.ts b/packages/api/src/validators/questionnaire.ts new file mode 100644 index 00000000..d6f289c3 --- /dev/null +++ b/packages/api/src/validators/questionnaire.ts @@ -0,0 +1,55 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const codingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const valueCodingSchema = z.object({ + system: z.string(), + code: z.string(), + display: z.string(), +}); + +const answerOptionSchema = z.object({ + valueCoding: valueCodingSchema, +}); + +const itemSchema = z.object({ + linkId: z.string(), + code: z.array(codingSchema), + text: z.string(), + type: z.string(), + repeats: z.boolean(), + answerOption: z.array(answerOptionSchema).optional(), +}); + +const questionnaireResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + name: z.string(), + status: z.string(), + description: z.string(), + code: z.array(codingSchema).optional(), + item: z.array(itemSchema).optional(), +}); + +const entrySchema = z.object({ + resource: questionnaireResourceSchema, +}); + +export const questionnaireBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/schedule.ts b/packages/api/src/validators/schedule.ts index 4c9d0a9b..83c68637 100644 --- a/packages/api/src/validators/schedule.ts +++ b/packages/api/src/validators/schedule.ts @@ -26,7 +26,7 @@ export const scheduleBundleSchema = z.object({ resourceType: z.enum(["Bundle"]), type: z.enum(["searchset"]), total: z.number(), - entry: z.array(entrySchema), + entry: z.array(entrySchema).optional(), }); // Usage: Validate data with bundleSchema.parse(yourDataObject) diff --git a/packages/api/src/validators/slot.ts b/packages/api/src/validators/slot.ts index a6a670ad..69fd36e5 100644 --- a/packages/api/src/validators/slot.ts +++ b/packages/api/src/validators/slot.ts @@ -22,5 +22,5 @@ export const slotBundleSchema = z.object({ resourceType: z.enum(["Bundle"]), type: z.enum(["searchset"]), total: z.number(), - entry: z.array(slotEntrySchema), + entry: z.array(slotEntrySchema).optional(), }); diff --git a/packages/api/src/validators/task.ts b/packages/api/src/validators/task.ts new file mode 100644 index 00000000..aacd2974 --- /dev/null +++ b/packages/api/src/validators/task.ts @@ -0,0 +1,74 @@ +import { z } from "zod"; + +const linkSchema = z.object({ + relation: z.string(), + url: z.string(), +}); + +const extensionSchema = z.object({ + url: z.string(), + valueReference: z + .object({ + reference: z.string(), + type: z.string(), + display: z.string(), + }) + .optional(), + valueString: z.string().optional(), +}); + +const referenceSchema = z.object({ + reference: z.string(), + type: z.string(), +}); + +const periodSchema = z.object({ + end: z.string().optional(), +}); + +const restrictionSchema = z.object({ + period: periodSchema.optional(), +}); + +const noteSchema = z.object({ + authorReference: referenceSchema.optional(), + time: z.string().optional(), + text: z.string().optional(), +}); + +const inputSchema = z.object({ + type: z.object({ + text: z.string(), + }), + valueString: z.string(), +}); + +export const taskResourceSchema = z.object({ + resourceType: z.string(), + id: z.string(), + extension: z.array(extensionSchema).optional(), + status: z.string(), + description: z.string().optional(), + for: referenceSchema.optional(), + authoredOn: z.string().optional(), + requester: referenceSchema.optional(), + owner: referenceSchema.optional(), + intent: z.string(), + restriction: restrictionSchema.optional(), + note: z.array(noteSchema).optional(), + input: z.array(inputSchema).optional(), +}); + +const entrySchema = z.object({ + resource: taskResourceSchema, +}); + +export const taskBundleSchema = z.object({ + resourceType: z.enum(["Bundle"]), + type: z.enum(["searchset"]), + total: z.number(), + link: z.array(linkSchema).optional(), + entry: z.array(entrySchema).optional(), +}); + +// Usage: Validate data with bundleSchema.parse(yourDataObject) From 9122d6943778b2bfbb3b1b5f08bec6dc6d7192ab Mon Sep 17 00:00:00 2001 From: ElektrikSpark Date: Mon, 11 Dec 2023 17:35:00 -0600 Subject: [PATCH 4/7] adding form items --- apps/expo/app.config.ts | 10 +- apps/expo/package.json | 14 +- .../portal/(tabs)/health-record/_layout.tsx | 3 +- .../app/portal/(tabs)/health-record/forms.tsx | 9 - .../(tabs)/health-record/forms/_layout.tsx | 50 ++ .../forms/consents/[consentId].tsx | 26 + .../health-record/forms/consents/_layout.tsx | 18 + .../health-record/forms/consents/index.tsx | 58 ++ .../(tabs)/health-record/forms/goals.tsx | 56 ++ .../(tabs)/health-record/forms/index.tsx | 41 ++ .../questionnaires/[questionnaireId].tsx | 41 ++ .../forms/questionnaires/_layout.tsx | 18 + .../forms/questionnaires/index.tsx | 63 ++ .../ui/health-record/consent-item.tsx | 37 ++ .../components/ui/health-record/form-item.tsx | 0 .../components/ui/health-record/goal-item.tsx | 37 ++ .../ui/health-record/questionnaire-item.tsx | 43 ++ .../components/ui/record-category-card.tsx | 2 +- apps/expo/src/components/ui/subpage-card.tsx | 22 + packages/api/src/canvas/canvas-client.ts | 46 +- .../api/src/router/patient-medical-history.ts | 15 +- packages/api/src/router/questionnaire.ts | 40 +- packages/api/src/validators/forms.ts | 27 +- .../src/validators/questionnaire-response.ts | 8 +- pnpm-lock.yaml | 579 +++++++++--------- 25 files changed, 920 insertions(+), 343 deletions(-) delete mode 100644 apps/expo/src/app/portal/(tabs)/health-record/forms.tsx create mode 100644 apps/expo/src/app/portal/(tabs)/health-record/forms/_layout.tsx create mode 100644 apps/expo/src/app/portal/(tabs)/health-record/forms/consents/[consentId].tsx create mode 100644 apps/expo/src/app/portal/(tabs)/health-record/forms/consents/_layout.tsx create mode 100644 apps/expo/src/app/portal/(tabs)/health-record/forms/consents/index.tsx create mode 100644 apps/expo/src/app/portal/(tabs)/health-record/forms/goals.tsx create mode 100644 apps/expo/src/app/portal/(tabs)/health-record/forms/index.tsx create mode 100644 apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/[questionnaireId].tsx create mode 100644 apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/_layout.tsx create mode 100644 apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/index.tsx create mode 100644 apps/expo/src/components/ui/health-record/consent-item.tsx create mode 100644 apps/expo/src/components/ui/health-record/form-item.tsx create mode 100644 apps/expo/src/components/ui/health-record/goal-item.tsx create mode 100644 apps/expo/src/components/ui/health-record/questionnaire-item.tsx create mode 100644 apps/expo/src/components/ui/subpage-card.tsx diff --git a/apps/expo/app.config.ts b/apps/expo/app.config.ts index f6c21c94..598bfdfa 100644 --- a/apps/expo/app.config.ts +++ b/apps/expo/app.config.ts @@ -28,11 +28,11 @@ const defineConfig = (): ExpoConfig => ({ backgroundColor: "#1F104A", }, }, - // extra: { - // eas: { - // projectId: "your-eas-project-id", - // }, - // }, + extra: { + eas: { + projectId: "8fced4f1-e004-428e-b6a0-1696bc85f6ce", + }, + }, experiments: { tsconfigPaths: true, typedRoutes: true, diff --git a/apps/expo/package.json b/apps/expo/package.json index a8c5a0a8..fc802817 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -28,21 +28,23 @@ "expo-constants": "~14.4.2", "expo-linking": "~5.0.2", "expo-router": "2.0.12", - "expo-secure-store": "^12.5.0", - "expo-splash-screen": "~0.22.0", - "expo-status-bar": "~1.7.1", - "expo-web-browser": "^12.5.0", + "expo-secure-store": "^12.3.1", + "expo-splash-screen": "~0.20.5", + "expo-status-bar": "~1.6.0", + "expo-web-browser": "^12.3.2", "jotai": "^2.6.0", "lucide-react-native": "^0.294.0", "nativewind": "^4.0.13", "react": "18.2.0", "react-dom": "18.2.0", - "react-native": "0.72.7", + "react-native": "0.72.6", + "react-native-blob-util": "^0.19.6", "react-native-gesture-handler": "~2.12.0", + "react-native-pdf": "^6.7.3", "react-native-reanimated": "~3.3.0", "react-native-safe-area-context": "4.6.3", "react-native-screens": "~3.22.1", - "react-native-svg": "^14.1.0", + "react-native-svg": "^13.9.0", "superjson": "^2.2.1" }, "devDependencies": { diff --git a/apps/expo/src/app/portal/(tabs)/health-record/_layout.tsx b/apps/expo/src/app/portal/(tabs)/health-record/_layout.tsx index b386609c..65f0ed9e 100644 --- a/apps/expo/src/app/portal/(tabs)/health-record/_layout.tsx +++ b/apps/expo/src/app/portal/(tabs)/health-record/_layout.tsx @@ -1,4 +1,3 @@ -import { Text } from "react-native"; import { Stack } from "expo-router"; import { @@ -40,7 +39,7 @@ export default function HealthRecordLayout() { headerStyle: { backgroundColor: "#fff", }, - headerLeft: () => , + headerShown: false, }} /> - Forms - - ); -} diff --git a/apps/expo/src/app/portal/(tabs)/health-record/forms/_layout.tsx b/apps/expo/src/app/portal/(tabs)/health-record/forms/_layout.tsx new file mode 100644 index 00000000..0babe8b0 --- /dev/null +++ b/apps/expo/src/app/portal/(tabs)/health-record/forms/_layout.tsx @@ -0,0 +1,50 @@ +import { Stack } from "expo-router"; + +import { LeftHeaderBack } from "~/components/ui/tabs-header"; + +export default function FormsLayout() { + return ( + + , + }} + /> + , + }} + /> + + + + ); +} diff --git a/apps/expo/src/app/portal/(tabs)/health-record/forms/consents/[consentId].tsx b/apps/expo/src/app/portal/(tabs)/health-record/forms/consents/[consentId].tsx new file mode 100644 index 00000000..1d467fdb --- /dev/null +++ b/apps/expo/src/app/portal/(tabs)/health-record/forms/consents/[consentId].tsx @@ -0,0 +1,26 @@ +import { Text, View } from "react-native"; +import { Stack, useLocalSearchParams } from "expo-router"; + +import { + ChatRightHeaderClose, + MessagesLeftHeaderBack, +} from "~/components/ui/messages-header"; + +export default function ConsentPage() { + const { consentId } = useLocalSearchParams<{ + consentId: string; + }>(); + return ( + + , + headerRight: () => , + }} + /> + Consent Page + {consentId} + + ); +} diff --git a/apps/expo/src/app/portal/(tabs)/health-record/forms/consents/_layout.tsx b/apps/expo/src/app/portal/(tabs)/health-record/forms/consents/_layout.tsx new file mode 100644 index 00000000..69bf21c5 --- /dev/null +++ b/apps/expo/src/app/portal/(tabs)/health-record/forms/consents/_layout.tsx @@ -0,0 +1,18 @@ +import { Stack } from "expo-router"; + +import { LeftHeaderBack } from "~/components/ui/tabs-header"; + +export default function ConsentsLayout() { + return ( + + , + }} + /> + + ); +} diff --git a/apps/expo/src/app/portal/(tabs)/health-record/forms/consents/index.tsx b/apps/expo/src/app/portal/(tabs)/health-record/forms/consents/index.tsx new file mode 100644 index 00000000..8890dd05 --- /dev/null +++ b/apps/expo/src/app/portal/(tabs)/health-record/forms/consents/index.tsx @@ -0,0 +1,58 @@ +import { Text, View } from "react-native"; +import { FlashList } from "@shopify/flash-list"; +import { useAtom } from "jotai"; + +import ConsentItem from "~/components/ui/health-record/consent-item"; +import { api } from "~/utils/api"; +import { patientAtom } from "../../allergies"; + +export default function ConsentsPage() { + const [patientId] = useAtom(patientAtom); + + const { isLoading, isError, data, error } = + api.patientMedicalHistory.getPatientConsents.useQuery({ + patientId, + }); + + if (isLoading) { + return Loading...; + } + + if (isError) { + return Error: {error.message}; + } + + const consents = data?.entry; + + return ( + + {data?.total > 0 ? ( + ( + + )} + estimatedItemSize={100} + keyExtractor={(item, index) => index.toString()} + contentContainerStyle={{ + paddingBottom: 16, + // paddingTop: 16, + // paddingHorizontal: 16, + }} + /> + ) : ( + {`No consents found.`} + )} + + ); +} diff --git a/apps/expo/src/app/portal/(tabs)/health-record/forms/goals.tsx b/apps/expo/src/app/portal/(tabs)/health-record/forms/goals.tsx new file mode 100644 index 00000000..1abb28d7 --- /dev/null +++ b/apps/expo/src/app/portal/(tabs)/health-record/forms/goals.tsx @@ -0,0 +1,56 @@ +import { Text, View } from "react-native"; +import { FlashList } from "@shopify/flash-list"; +import { useAtom } from "jotai"; + +import GoalItem from "~/components/ui/health-record/goal-item"; +import { api } from "~/utils/api"; +import { patientAtom } from "../allergies"; + +export default function GoalsPage() { + const [patientId] = useAtom(patientAtom); + + const { isLoading, isError, data, error } = + api.patientMedicalHistory.getPatientGoals.useQuery({ + patientId, + }); + if (isLoading) { + return Loading...; + } + + if (isError) { + return Error: {error.message}; + } + + const goals = data?.entry; + + return ( + + {data?.total > 0 ? ( + ( + + )} + estimatedItemSize={100} + keyExtractor={(item, index) => index.toString()} + contentContainerStyle={{ + paddingBottom: 16, + // paddingTop: 16, + // paddingHorizontal: 16, + }} + /> + ) : ( + {`No goals found.`} + )} + + ); +} diff --git a/apps/expo/src/app/portal/(tabs)/health-record/forms/index.tsx b/apps/expo/src/app/portal/(tabs)/health-record/forms/index.tsx new file mode 100644 index 00000000..0009a002 --- /dev/null +++ b/apps/expo/src/app/portal/(tabs)/health-record/forms/index.tsx @@ -0,0 +1,41 @@ +import { View } from "react-native"; +import { router } from "expo-router"; +import { FlashList } from "@shopify/flash-list"; + +import { SubpageCard } from "~/components/ui/subpage-card"; + +const items = [ + { + title: "Goals", + onPress: () => router.push("/portal/(tabs)/health-record/forms/goals"), + }, + { + title: "Questionnaires", + onPress: () => + router.push("/portal/(tabs)/health-record/forms/questionnaires"), + }, + { + title: "Consents", + onPress: () => router.push("/portal/(tabs)/health-record/forms/consents"), + }, +]; + +export default function Forms() { + return ( + + ( + + )} + estimatedItemSize={100} + keyExtractor={(item, index) => index.toString()} + contentContainerStyle={{ + paddingBottom: 16, + paddingTop: 16, + paddingHorizontal: 16, + }} + /> + + ); +} diff --git a/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/[questionnaireId].tsx b/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/[questionnaireId].tsx new file mode 100644 index 00000000..3b381c24 --- /dev/null +++ b/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/[questionnaireId].tsx @@ -0,0 +1,41 @@ +import { Text, View } from "react-native"; +import { Stack, useLocalSearchParams } from "expo-router"; + +import { + ChatRightHeaderClose, + MessagesLeftHeaderBack, +} from "~/components/ui/messages-header"; +import { api } from "~/utils/api"; + +export default function QuesitonnairePage() { + const { questionnaireId } = useLocalSearchParams<{ + questionnaireId: string; + }>(); + + const { isLoading, isError, data, error } = + api.questionnaire.getQuestionnaireResponse.useQuery({ + id: questionnaireId, + }); + + if (isLoading) { + return Loading...; + } + + if (isError) { + return Error: {error.message}; + } + + return ( + + , + headerRight: () => , + }} + /> + Questionnaire Page + {questionnaireId} + + ); +} diff --git a/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/_layout.tsx b/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/_layout.tsx new file mode 100644 index 00000000..e8a1b4a3 --- /dev/null +++ b/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/_layout.tsx @@ -0,0 +1,18 @@ +import { Stack } from "expo-router"; + +import { LeftHeaderBack } from "~/components/ui/tabs-header"; + +export default function QuestionnairesLayout() { + return ( + + , + }} + /> + + ); +} diff --git a/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/index.tsx b/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/index.tsx new file mode 100644 index 00000000..c3ec940e --- /dev/null +++ b/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/index.tsx @@ -0,0 +1,63 @@ +import { Text, View } from "react-native"; +import { useRouter } from "expo-router"; +import { FlashList } from "@shopify/flash-list"; +import { useAtom } from "jotai"; + +import QuestionnaireItem from "~/components/ui/health-record/questionnaire-item"; +import { api } from "~/utils/api"; +import { patientAtom } from "../../allergies"; + +export default function QuestionnairesPage() { + const [patientId] = useAtom(patientAtom); + const router = useRouter(); + + const { isLoading, isError, data, error } = + api.patientMedicalHistory.getPatientQuestionnaireResponses.useQuery({ + patientId, + }); + + if (isLoading) { + return Loading...; + } + + if (isError) { + return Error: {error.message}; + } + + const responses = data?.entry; + + return ( + + {data?.total > 0 ? ( + ( + + router.push( + `/portal/(tabs)/health-record/forms/questionnaires/${item.resource?.id}`, + ) + } + first={index === 0} + last={index === data?.total - 1} + /> + )} + estimatedItemSize={100} + keyExtractor={(item, index) => index.toString()} + contentContainerStyle={{ + paddingBottom: 16, + // paddingTop: 16, + // paddingHorizontal: 16, + }} + /> + ) : ( + {`No questionnaires found.`} + )} + + ); +} diff --git a/apps/expo/src/components/ui/health-record/consent-item.tsx b/apps/expo/src/components/ui/health-record/consent-item.tsx new file mode 100644 index 00000000..b10bfb5d --- /dev/null +++ b/apps/expo/src/components/ui/health-record/consent-item.tsx @@ -0,0 +1,37 @@ +import { Text, View } from "react-native"; +import { clsx } from "clsx"; + +export default function ConsentItem({ + consent, + status, + start, + end, + first, + last, +}: { + consent: string; + status: string; + start: string; + end: string; + first?: boolean; + last?: boolean; +}) { + return ( + + + {consent} + + Status: {status} + Start: {start} + End: {end} + + + + ); +} diff --git a/apps/expo/src/components/ui/health-record/form-item.tsx b/apps/expo/src/components/ui/health-record/form-item.tsx new file mode 100644 index 00000000..e69de29b diff --git a/apps/expo/src/components/ui/health-record/goal-item.tsx b/apps/expo/src/components/ui/health-record/goal-item.tsx new file mode 100644 index 00000000..7f848a75 --- /dev/null +++ b/apps/expo/src/components/ui/health-record/goal-item.tsx @@ -0,0 +1,37 @@ +import { Text, View } from "react-native"; +import { clsx } from "clsx"; + +export default function GoalItem({ + goal, + status, + start, + priority, + first, + last, +}: { + goal: string; + status: string; + start: string; + priority: string; + first?: boolean; + last?: boolean; +}) { + return ( + + + {goal} + + Status: {status} + Start: {start} + Priority: {priority} + + + + ); +} diff --git a/apps/expo/src/components/ui/health-record/questionnaire-item.tsx b/apps/expo/src/components/ui/health-record/questionnaire-item.tsx new file mode 100644 index 00000000..44b8d824 --- /dev/null +++ b/apps/expo/src/components/ui/health-record/questionnaire-item.tsx @@ -0,0 +1,43 @@ +import { Text, TouchableOpacity, View } from "react-native"; +import { clsx } from "clsx"; +import { ChevronRight } from "lucide-react-native"; + +export default function QuestionnaireItem({ + questionnaireResponse, + status, + authored, + onPress, + first, + last, +}: { + questionnaireResponse: string; + status: string; + authored: string; + onPress: () => void; + first?: boolean; + last?: boolean; +}) { + return ( + + + + {questionnaireResponse} + + Status: {status} + Authored: {authored} + + + + + + + ); +} diff --git a/apps/expo/src/components/ui/record-category-card.tsx b/apps/expo/src/components/ui/record-category-card.tsx index b58f3580..d12c0972 100644 --- a/apps/expo/src/components/ui/record-category-card.tsx +++ b/apps/expo/src/components/ui/record-category-card.tsx @@ -1,4 +1,4 @@ -import { Text, TouchableHighlight, TouchableOpacity, View } from "react-native"; +import { Text, TouchableOpacity, View } from "react-native"; import { ChevronRight } from "lucide-react-native"; import type { LucideIcon } from "lucide-react-native"; diff --git a/apps/expo/src/components/ui/subpage-card.tsx b/apps/expo/src/components/ui/subpage-card.tsx new file mode 100644 index 00000000..9dd3697a --- /dev/null +++ b/apps/expo/src/components/ui/subpage-card.tsx @@ -0,0 +1,22 @@ +import { Text, TouchableOpacity, View } from "react-native"; +import { ChevronRight } from "lucide-react-native"; + +const SubpageCard = ({ + title, + onPress, +}: { + title: string; + onPress: () => void; +}) => { + return ( + + + {title} + + + + + ); +}; + +export { SubpageCard }; diff --git a/packages/api/src/canvas/canvas-client.ts b/packages/api/src/canvas/canvas-client.ts index 03dc1d3b..3614ee91 100644 --- a/packages/api/src/canvas/canvas-client.ts +++ b/packages/api/src/canvas/canvas-client.ts @@ -1,6 +1,14 @@ import z from "zod"; +import { + allergenBundleSchema, + allergenResourceSchema, +} from "../validators/allergen"; import { searchAllergyIntoleranceBundleSchema } from "../validators/allergy-intolerance"; +import { + appointmentBundleSchema, + appointmentResourceSchema, +} from "../validators/appointment"; import { careTeamBundleSchema, careTeamResourceSchema, @@ -11,10 +19,18 @@ import { } from "../validators/communication"; import { searchConditionBundleSchema } from "../validators/condition"; import { coverageBundleSchema } from "../validators/coverage"; +import { + diagnosticReportBundleSchema, + diagnosticReportResourceSchema, +} from "../validators/diagnostic-report"; import { documentReferenceBundleSchema, documentReferenceResourceSchema, } from "../validators/document-reference"; +import { + encounterBundleSchema, + encounterResourceSchema, +} from "../validators/encounter"; import { goalBundleSchema, goalResourceSchema } from "../validators/goal"; import { searchImmunizationsBundleSchema } from "../validators/immunization"; import { searchMedicationBundleSchema } from "../validators/medication"; @@ -29,8 +45,13 @@ import { practitionerBundleSchema, practitionerResourceSchema, } from "../validators/practitioner"; +import { + questionnaireResponseBundleSchema, + questionnaireResponseResourceSchema, +} from "../validators/questionnaire-response"; import { scheduleBundleSchema } from "../validators/schedule"; import { slotBundleSchema } from "../validators/slot"; +import { taskBundleSchema } from "../validators/task"; export type post_GetAnOauthToken = typeof post_GetAnOauthToken; export const post_GetAnOauthToken = { @@ -55,7 +76,7 @@ export const get_ReadAllergen = { allergen_id: z.string(), }), }), - response: z.unknown(), + response: allergenResourceSchema, }; export type get_SearchAllergen = typeof get_SearchAllergen; @@ -68,7 +89,7 @@ export const get_SearchAllergen = { _text: z.string().optional(), }), }), - response: z.unknown(), + response: allergenBundleSchema, }; export type get_SearchAllergyintolerance = typeof get_SearchAllergyintolerance; @@ -315,7 +336,7 @@ export const get_SearchAppointment = { }), body: z.unknown(), }), - response: z.unknown(), + response: appointmentBundleSchema, }; export type post_CreateAppointment = typeof post_CreateAppointment; @@ -388,7 +409,7 @@ export const get_ReadAppointment = { appointment_id: z.string(), }), }), - response: z.unknown(), + response: appointmentResourceSchema, }; export type put_UpdateAppointment = typeof put_UpdateAppointment; @@ -1057,7 +1078,7 @@ export const get_SearchConsent = { ) .optional(), resourceType: z.string().optional(), - total: z.number().optional(), + total: z.number(), type: z.string().optional(), }), }; @@ -1475,7 +1496,7 @@ export const get_ReadDiagnosticreport = { diagnostic_report_id: z.string(), }), }), - response: z.unknown(), + response: diagnosticReportResourceSchema, }; export type get_SearchDiagnosticreport = typeof get_SearchDiagnosticreport; @@ -1490,7 +1511,7 @@ export const get_SearchDiagnosticreport = { code: z.string().optional(), }), }), - response: z.unknown(), + response: diagnosticReportBundleSchema, }; export type get_ReadDocumentreference = typeof get_ReadDocumentreference; @@ -1531,7 +1552,7 @@ export const get_ReadEncounter = { encounter_id: z.string(), }), }), - response: z.unknown(), + response: encounterResourceSchema, }; export type get_SearchEncounter = typeof get_SearchEncounter; @@ -1545,7 +1566,7 @@ export const get_SearchEncounter = { patient: z.string().optional(), }), }), - response: z.unknown(), + response: encounterBundleSchema, }; export type get_ReadGoal = typeof get_ReadGoal; @@ -2962,6 +2983,7 @@ export const get_SearchQuestionnaire = { }), }; +// TODO - bugged generation, need to split the search and update export type get_UpdateQuestionnaireresponse = typeof get_UpdateQuestionnaireresponse; export const get_UpdateQuestionnaireresponse = { @@ -3017,7 +3039,7 @@ export const get_UpdateQuestionnaireresponse = { .optional(), }), }), - response: z.unknown(), + response: questionnaireResponseBundleSchema, }; export type post_CreateQuestionnaireresponse = @@ -3079,7 +3101,7 @@ export const get_ReadQuestionnaireresponse = { questionnaire_response_id: z.string(), }), }), - response: z.unknown(), + response: questionnaireResponseResourceSchema, }; export type get_SearchSchedule = typeof get_SearchSchedule; @@ -3117,7 +3139,7 @@ export const get_SearchTask = { owner: z.string().optional(), }), }), - response: z.unknown(), + response: taskBundleSchema, }; export type post_CreateTask = typeof post_CreateTask; diff --git a/packages/api/src/router/patient-medical-history.ts b/packages/api/src/router/patient-medical-history.ts index 75aee28e..64a13d53 100644 --- a/packages/api/src/router/patient-medical-history.ts +++ b/packages/api/src/router/patient-medical-history.ts @@ -4,8 +4,11 @@ import { z } from "zod"; import { get_SearchAllergyintolerance, get_SearchCondition, + get_SearchConsent, + get_SearchGoal, get_SearchImmunization, get_SearchMedicationstatement, + get_UpdateQuestionnaireresponse, } from "../canvas/canvas-client"; import { createTRPCRouter, protectedCanvasProcedure } from "../trpc"; @@ -59,6 +62,7 @@ export const patientMedicalHistoryRouter = createTRPCRouter({ patient: patientId, }, }); + return appointmentsData; } catch (error) { console.error(error); @@ -117,7 +121,8 @@ export const patientMedicalHistoryRouter = createTRPCRouter({ patient: patientId, }, }); - return consentsData; + const validatedData = get_SearchConsent.response.parse(consentsData); + return validatedData; } catch (error) { console.error(error); throw new TRPCError({ @@ -145,7 +150,8 @@ export const patientMedicalHistoryRouter = createTRPCRouter({ patient: patientId, }, }); - return goalsData; + const validatedData = get_SearchGoal.response.parse(goalsData); + return validatedData; } catch (error) { console.error(error); throw new TRPCError({ @@ -177,7 +183,10 @@ export const patientMedicalHistoryRouter = createTRPCRouter({ body: {}, // TODO - remove }, ); - return questionnaireResponsesData; + const validatedData = get_UpdateQuestionnaireresponse.response.parse( + questionnaireResponsesData, + ); + return validatedData; } catch (error) { console.error(error); throw new TRPCError({ diff --git a/packages/api/src/router/questionnaire.ts b/packages/api/src/router/questionnaire.ts index aae833fc..b94f3f99 100644 --- a/packages/api/src/router/questionnaire.ts +++ b/packages/api/src/router/questionnaire.ts @@ -1,10 +1,12 @@ import { TRPCError } from "@trpc/server"; import { z } from "zod"; +import { get_ReadQuestionnaireresponse } from "../canvas/canvas-client"; import { createTRPCRouter, protectedCanvasProcedure } from "../trpc"; -import { questionnaireResponseBodySchema } from "../validators"; +import { questionnaireResponseResourceSchema } from "../validators/questionnaire-response"; export const questionnaireRouter = createTRPCRouter({ + // Questionnaire getQuestionnaire: protectedCanvasProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) => { @@ -34,8 +36,42 @@ export const questionnaireRouter = createTRPCRouter({ }); } }), + + // QuestionnaireResponse + getQuestionnaireResponse: protectedCanvasProcedure + .input(z.object({ id: z.string() })) + .query(async ({ ctx, input }) => { + const { api, canvasToken } = ctx; + const { id } = input; + + if (!canvasToken) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Canvas token is missing", + }); + } + + try { + const questionnaireResponseData = await api.get( + "/QuestionnaireResponse/{questionnaire_response_id}", + { + path: { questionnaire_response_id: id }, + }, + ); + const validatedData = get_ReadQuestionnaireresponse.response.parse( + questionnaireResponseData, + ); + return validatedData; + } catch (error) { + // Handle any other errors + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "An error occurred while fetching questionnaire data", + }); + } + }), submitQuestionnaireResponse: protectedCanvasProcedure - .input(z.object({ body: questionnaireResponseBodySchema })) + .input(z.object({ body: questionnaireResponseResourceSchema })) .mutation(async ({ ctx, input }) => { const { api, canvasToken } = ctx; const { body } = input; diff --git a/packages/api/src/validators/forms.ts b/packages/api/src/validators/forms.ts index bfb4d200..b1361115 100644 --- a/packages/api/src/validators/forms.ts +++ b/packages/api/src/validators/forms.ts @@ -1,8 +1,9 @@ import { z } from "zod"; import type { get_ReadQuestionnaire } from "../canvas/canvas-client"; +import { valueCodingSchema } from "./questionnaire-response"; -// Intake Forms +// Intake forms export const newPatientSchema = z.object({ name: z.string().min(2, "Name must be at least 2 characters"), address: z.string().min(10, "Address must be at least 10 characters"), @@ -10,7 +11,7 @@ export const newPatientSchema = z.object({ }); export type NewPatient = z.infer; -// Consent +// Consent forms export const consentFormSchema = z.object({ generic: z .boolean() @@ -27,7 +28,7 @@ export const consentFormSchema = z.object({ }); export type ConsentForm = z.infer; -// Coverage +// Coverage forms export const coverageFormSchema = z.object({ subscriberId: z.string().refine((value) => value.length > 0, { message: "Can't be blank.", @@ -39,13 +40,6 @@ export const coverageFormSchema = z.object({ export type CoverageForm = z.infer; // Questionnaires -export const valueCodingSchema = z.object({ - code: z.string(), - display: z.string(), - system: z.string(), -}); -export type ValueCoding = z.infer; - export const questionItemSchema = z.object({ linkId: z.string(), text: z.string(), @@ -60,19 +54,6 @@ export const questionItemSchema = z.object({ }); export type QuestionItem = z.infer; -export const questionnaireResponseBodySchema = z.object({ - questionnaire: z.string(), - status: z.string(), - subject: z.object({ - reference: z.string(), - type: z.string(), - }), - item: z.array(questionItemSchema), -}); -export type QuestionnaireResponseBody = z.infer< - typeof questionnaireResponseBodySchema ->; - export function generateQuestionnaireSchema( questionnaire: z.infer, ) { diff --git a/packages/api/src/validators/questionnaire-response.ts b/packages/api/src/validators/questionnaire-response.ts index a8d5f72c..d14de182 100644 --- a/packages/api/src/validators/questionnaire-response.ts +++ b/packages/api/src/validators/questionnaire-response.ts @@ -15,11 +15,12 @@ const referenceSchema = z.object({ type: z.string(), }); -const valueCodingSchema = z.object({ +export const valueCodingSchema = z.object({ system: z.string(), code: z.string(), display: z.string(), }); +export type ValueCoding = z.infer; const answerSchema = z.object({ valueCoding: valueCodingSchema.optional(), @@ -32,7 +33,7 @@ const itemSchema = z.object({ answer: z.array(answerSchema).optional(), }); -const questionnaireResponseResourceSchema = z.object({ +export const questionnaireResponseResourceSchema = z.object({ resourceType: z.string(), id: z.string(), extension: z.array(extensionSchema).optional(), @@ -44,6 +45,9 @@ const questionnaireResponseResourceSchema = z.object({ author: referenceSchema.optional(), item: z.array(itemSchema).optional(), }); +export type QuestionnaireResponseResource = z.infer< + typeof questionnaireResponseResourceSchema +>; const entrySchema = z.object({ resource: questionnaireResponseResourceSchema, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 171d1c98..d5b882c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,10 +65,10 @@ importers: version: 0.10.7 '@shopify/flash-list': specifier: 1.4.3 - version: 1.4.3(@babel/runtime@7.23.4)(react-native@0.72.7)(react@18.2.0) + version: 1.4.3(@babel/runtime@7.23.4)(react-native@0.72.6)(react@18.2.0) '@tanstack/react-query': specifier: ^4.36.1 - version: 4.36.1(react-dom@18.2.0)(react-native@0.72.7)(react@18.2.0) + version: 4.36.1(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0) '@trpc/client': specifier: ^10.43.6 version: 10.44.1(@trpc/server@10.44.1) @@ -92,28 +92,28 @@ importers: version: 5.0.2(expo@49.0.21) expo-router: specifier: 2.0.12 - version: 2.0.12(expo-constants@14.4.2)(expo-linking@5.0.2)(expo-modules-autolinking@1.5.1)(expo-status-bar@1.7.1)(expo@49.0.21)(metro@0.76.8)(react-dom@18.2.0)(react-native-gesture-handler@2.12.1)(react-native-reanimated@3.3.0)(react-native-safe-area-context@4.6.3)(react-native-screens@3.22.1)(react-native@0.72.7)(react@18.2.0) + version: 2.0.12(expo-constants@14.4.2)(expo-linking@5.0.2)(expo-modules-autolinking@1.5.1)(expo-status-bar@1.6.0)(expo@49.0.21)(metro@0.76.8)(react-dom@18.2.0)(react-native-gesture-handler@2.12.1)(react-native-reanimated@3.3.0)(react-native-safe-area-context@4.6.3)(react-native-screens@3.22.1)(react-native@0.72.6)(react@18.2.0) expo-secure-store: - specifier: ^12.5.0 - version: 12.5.0(expo@49.0.21) + specifier: ^12.3.1 + version: 12.3.1(expo@49.0.21) expo-splash-screen: - specifier: ~0.22.0 - version: 0.22.0(expo-modules-autolinking@1.5.1)(expo@49.0.21) + specifier: ~0.20.5 + version: 0.20.5(expo-modules-autolinking@1.5.1)(expo@49.0.21) expo-status-bar: - specifier: ~1.7.1 - version: 1.7.1 + specifier: ~1.6.0 + version: 1.6.0 expo-web-browser: - specifier: ^12.5.0 - version: 12.5.0(expo@49.0.21) + specifier: ^12.3.2 + version: 12.3.2(expo@49.0.21) jotai: specifier: ^2.6.0 version: 2.6.0(@types/react@18.2.39)(react@18.2.0) lucide-react-native: specifier: ^0.294.0 - version: 0.294.0(prop-types@15.8.1)(react-native-svg@14.1.0)(react-native@0.72.7)(react@18.2.0) + version: 0.294.0(prop-types@15.8.1)(react-native-svg@13.9.0)(react-native@0.72.6)(react@18.2.0) nativewind: specifier: ^4.0.13 - version: 4.0.16(@babel/core@7.23.3)(react-native-reanimated@3.3.0)(react-native-safe-area-context@4.6.3)(react-native-svg@14.1.0)(react-native@0.72.7)(react@18.2.0)(tailwindcss@3.3.5) + version: 4.0.16(@babel/core@7.23.3)(react-native-reanimated@3.3.0)(react-native-safe-area-context@4.6.3)(react-native-svg@13.9.0)(react-native@0.72.6)(react@18.2.0)(tailwindcss@3.3.5) react: specifier: 18.2.0 version: 18.2.0 @@ -121,23 +121,29 @@ importers: specifier: 18.2.0 version: 18.2.0(react@18.2.0) react-native: - specifier: 0.72.7 - version: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + specifier: 0.72.6 + version: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native-blob-util: + specifier: ^0.19.6 + version: 0.19.6(react-native@0.72.6)(react@18.2.0) react-native-gesture-handler: specifier: ~2.12.0 - version: 2.12.1(react-native@0.72.7)(react@18.2.0) + version: 2.12.1(react-native@0.72.6)(react@18.2.0) + react-native-pdf: + specifier: ^6.7.3 + version: 6.7.3(react-native-blob-util@0.19.6)(react-native@0.72.6)(react@18.2.0) react-native-reanimated: specifier: ~3.3.0 - version: 3.3.0(@babel/core@7.23.3)(@babel/plugin-proposal-nullish-coalescing-operator@7.18.6)(@babel/plugin-proposal-optional-chaining@7.21.0)(@babel/plugin-transform-arrow-functions@7.23.3)(@babel/plugin-transform-shorthand-properties@7.23.3)(@babel/plugin-transform-template-literals@7.23.3)(react-native@0.72.7)(react@18.2.0) + version: 3.3.0(@babel/core@7.23.3)(@babel/plugin-proposal-nullish-coalescing-operator@7.18.6)(@babel/plugin-proposal-optional-chaining@7.21.0)(@babel/plugin-transform-arrow-functions@7.23.3)(@babel/plugin-transform-shorthand-properties@7.23.3)(@babel/plugin-transform-template-literals@7.23.3)(react-native@0.72.6)(react@18.2.0) react-native-safe-area-context: specifier: 4.6.3 - version: 4.6.3(react-native@0.72.7)(react@18.2.0) + version: 4.6.3(react-native@0.72.6)(react@18.2.0) react-native-screens: specifier: ~3.22.1 - version: 3.22.1(react-native@0.72.7)(react@18.2.0) + version: 3.22.1(react-native@0.72.6)(react@18.2.0) react-native-svg: - specifier: ^14.1.0 - version: 14.1.0(react-native@0.72.7)(react@18.2.0) + specifier: ^13.9.0 + version: 13.9.0(react-native@0.72.6)(react@18.2.0) superjson: specifier: ^2.2.1 version: 2.2.1 @@ -216,7 +222,7 @@ importers: version: 0.7.1(typescript@5.3.2)(zod@3.22.4) '@tanstack/react-query': specifier: ^4.36.1 - version: 4.36.1(react-dom@18.2.0)(react-native@0.72.7)(react@18.2.0) + version: 4.36.1(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0) '@trpc/client': specifier: ^10.43.6 version: 10.44.1(@trpc/server@10.44.1) @@ -2050,12 +2056,12 @@ packages: '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - /@bacons/react-views@1.1.3(react-native@0.72.7): + /@bacons/react-views@1.1.3(react-native@0.72.6): resolution: {integrity: sha512-aLipQAkQKRzG64e28XHBpByyBPfANz0A6POqYHGyryHizG9vLCLNQwLe8gwFANEMBWW2Mx5YdQ7RkNdQMQ+CXQ==} peerDependencies: react-native: '*' dependencies: - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) dev: false /@cloudflare/kv-asset-handler@0.2.0: @@ -2921,58 +2927,9 @@ packages: transitivePeerDependencies: - supports-color - /@expo/config-plugins@7.4.0: - resolution: {integrity: sha512-/BwYRl6QQ9ZKYpVaIqHE5sSPqNZI9CUtfLfYHhpnShQUA1KHRMi6y9zjb3IXJisk0/fcrtRm2yP3A7F0l304sQ==} - dependencies: - '@expo/config-types': 50.0.0-canary-20231125-d600e44 - '@expo/json-file': 8.2.37 - '@expo/plist': 0.0.20 - '@expo/sdk-runtime-versions': 1.0.0 - '@react-native/normalize-color': 2.1.0 - chalk: 4.1.2 - debug: 4.3.4 - find-up: 5.0.0 - getenv: 1.0.0 - glob: 7.1.6 - resolve-from: 5.0.0 - semver: 7.5.3 - slash: 3.0.0 - xcode: 3.0.1 - xml2js: 0.6.0 - transitivePeerDependencies: - - supports-color - dev: false - - /@expo/config-plugins@7.5.0: - resolution: {integrity: sha512-qOKjmgbddLh1vj9ytUT6AduhEans2cHgS42nopVgh5Wz8X+QUvPcCr1Yc8MvLM3OlbswBMCJceeosZa463i0uA==} - dependencies: - '@expo/config-types': 50.0.0-canary-20231125-d600e44 - '@expo/fingerprint': 0.2.0 - '@expo/json-file': 8.2.37 - '@expo/plist': 0.0.20 - '@expo/sdk-runtime-versions': 1.0.0 - '@react-native/normalize-color': 2.1.0 - chalk: 4.1.2 - debug: 4.3.4 - find-up: 5.0.0 - getenv: 1.0.0 - glob: 7.1.6 - resolve-from: 5.0.0 - semver: 7.5.3 - slash: 3.0.0 - xcode: 3.0.1 - xml2js: 0.6.0 - transitivePeerDependencies: - - supports-color - dev: false - /@expo/config-types@49.0.0: resolution: {integrity: sha512-8eyREVi+K2acnMBe/rTIu1dOfyR2+AMnTLHlut+YpMV9OZPdeKV0Bs9BxAewGqBA2slslbQ9N39IS2CuTKpXkA==} - /@expo/config-types@50.0.0-canary-20231125-d600e44: - resolution: {integrity: sha512-OjIHA45zyt+gN5gUN1ZswCKJasADKB3zY9VIRnyH77fH84HVesIomUwQBy0vmuudQZ1X5JRd36JyWjNt5JNUIQ==} - dev: false - /@expo/config@8.1.2: resolution: {integrity: sha512-4e7hzPj50mQIlsrzOH6XZ36O094mPfPTIDIH4yv49bWNMc7GFLTofB/lcT+QyxiLaJuC0Wlk9yOLB8DIqmtwug==} dependencies: @@ -2991,24 +2948,6 @@ packages: - supports-color dev: false - /@expo/config@8.3.1: - resolution: {integrity: sha512-5fNGAw5h/MDOc8Ulv9nonafPtOT042B7dF6vrVxSP3CY5qiVu0tCsmbL412wEcrAZ8MY7UMv9e6IzpGTgleYgg==} - dependencies: - '@babel/code-frame': 7.10.4 - '@expo/config-plugins': 7.5.0 - '@expo/config-types': 50.0.0-canary-20231125-d600e44 - '@expo/json-file': 8.2.37 - getenv: 1.0.0 - glob: 7.1.6 - require-from-string: 2.0.2 - resolve-from: 5.0.0 - semver: 7.5.3 - slugify: 1.6.6 - sucrase: 3.34.0 - transitivePeerDependencies: - - supports-color - dev: false - /@expo/dev-server@0.5.5: resolution: {integrity: sha512-t0fT8xH1exwYsH5hh7bAt85VF+gXxg24qrbny2rR/iKoPTWFCd2JNQV8pvfLg51hvrywQ3YCBuT3lU1w7aZxFA==} dependencies: @@ -3064,21 +3003,6 @@ packages: - supports-color dev: false - /@expo/fingerprint@0.2.0: - resolution: {integrity: sha512-k6MhJTrX4CYEwsyGemiLT8rnBwjRBYe0eKYAM3kqw0WbSHzkOJm739sgdswGLmA53iiX6FbB1TsiLnqt+h2U2w==} - hasBin: true - dependencies: - '@expo/spawn-async': 1.7.2 - chalk: 4.1.2 - debug: 4.3.4 - find-up: 5.0.0 - minimatch: 3.1.2 - p-limit: 3.1.0 - resolve-from: 5.0.0 - transitivePeerDependencies: - - supports-color - dev: false - /@expo/image-utils@0.3.22: resolution: {integrity: sha512-uzq+RERAtkWypOFOLssFnXXqEqKjNj9eXN7e97d/EXUAojNcLDoXc0sL+F5B1I4qtlsnhX01kcpoIBBZD8wZNQ==} dependencies: @@ -3123,14 +3047,14 @@ packages: - supports-color dev: false - /@expo/metro-runtime@2.2.14(react-native@0.72.7): + /@expo/metro-runtime@2.2.14(react-native@0.72.6): resolution: {integrity: sha512-qfhv5VPbYYZO1OVZixtxgl8Tal00K+QkDT2x719glF96v4g1ApvMmwfTuUsh3Y3GjtQpWhPC1myrjsZjyHdFnQ==} peerDependencies: react-native: '*' dependencies: - '@bacons/react-views': 1.1.3(react-native@0.72.7) + '@bacons/react-views': 1.1.3(react-native@0.72.6) qs: 6.11.2 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) dev: false /@expo/ngrok-bin-darwin-arm64@2.3.41: @@ -3300,27 +3224,6 @@ packages: - supports-color dev: false - /@expo/prebuild-config@6.4.0(expo-modules-autolinking@1.5.1): - resolution: {integrity: sha512-RjKK7rd2H9P1pTcAcZFUd9tpxCwFNyyrlTdHZWlvZvZnBJWIyUZex7P3q7db7KLJ6UrVPmlM+B7OSc0Mxs4uoQ==} - peerDependencies: - expo-modules-autolinking: '>=0.8.1' - dependencies: - '@expo/config': 8.3.1 - '@expo/config-plugins': 7.4.0 - '@expo/config-types': 50.0.0-canary-20231125-d600e44 - '@expo/image-utils': 0.3.22 - '@expo/json-file': 8.2.37 - debug: 4.3.4 - expo-modules-autolinking: 1.5.1 - fs-extra: 9.1.0 - resolve-from: 5.0.0 - semver: 7.5.3 - xml2js: 0.6.0 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - /@expo/rudder-sdk-node@1.1.1: resolution: {integrity: sha512-uy/hS/awclDJ1S88w9UGpc6Nm9XnNUjzOAAib1A3PVAnGQIwebg8DpFqOthFBTlZxeuV/BKbZ5jmTbtNZkp1WQ==} engines: {node: '>=12'} @@ -3346,13 +3249,6 @@ packages: cross-spawn: 6.0.5 dev: false - /@expo/spawn-async@1.7.2: - resolution: {integrity: sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew==} - engines: {node: '>=12'} - dependencies: - cross-spawn: 7.0.3 - dev: false - /@expo/vector-icons@13.0.0: resolution: {integrity: sha512-TI+l71+5aSKnShYclFa14Kum+hQMZ86b95SH6tQUG3qZEmLTarvWpKwqtTwQKqvlJSJrpFiSFu3eCuZokY6zWA==} dev: false @@ -3479,6 +3375,18 @@ packages: resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} dev: true + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: false + /@jest/create-cache-key-function@29.7.0: resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3897,6 +3805,13 @@ packages: '@parcel/watcher-win32-x64': 2.3.0 dev: true + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: false + optional: true + /@planetscale/database@1.11.0: resolution: {integrity: sha512-aWbU+D/IRHoDE9975y+Q4c+EwwAWxCPwFId+N1AhQVFXzbeJMkj6KN2iQtoi03elcLMRdfT+V3i9Z4WRw+/oIA==} engines: {node: '>=16'} @@ -4907,10 +4822,10 @@ packages: '@babel/runtime': 7.23.4 dev: false - /@react-native-community/cli-clean@11.3.10: - resolution: {integrity: sha512-g6QjW+DSqoWRHzmIQW3AH22k1AnynWuOdy2YPwYEGgPddTeXZtJphIpEVwDOiC0L4mZv2VmiX33/cGNUwO0cIA==} + /@react-native-community/cli-clean@11.3.7: + resolution: {integrity: sha512-twtsv54ohcRyWVzPXL3F9VHGb4Qhn3slqqRs3wEuRzjR7cTmV2TIO2b1VhaqF4HlCgNd+cGuirvLtK2JJyaxMg==} dependencies: - '@react-native-community/cli-tools': 11.3.10 + '@react-native-community/cli-tools': 11.3.7 chalk: 4.1.2 execa: 5.1.1 prompts: 2.4.2 @@ -4918,10 +4833,10 @@ packages: - encoding dev: false - /@react-native-community/cli-config@11.3.10: - resolution: {integrity: sha512-YYu14nm1JYLS6mDRBz78+zDdSFudLBFpPkhkOoj4LuBhNForQBIqFFHzQbd9/gcguJxfW3vlYSnudfaUI7oGLg==} + /@react-native-community/cli-config@11.3.7: + resolution: {integrity: sha512-FDBLku9xskS+bx0YFJFLCmUJhEZ4/MMSC9qPYOGBollWYdgE7k/TWI0IeYFmMALAnbCdKQAYP5N29N55Tad8lg==} dependencies: - '@react-native-community/cli-tools': 11.3.10 + '@react-native-community/cli-tools': 11.3.7 chalk: 4.1.2 cosmiconfig: 5.2.1 deepmerge: 4.3.1 @@ -4931,21 +4846,21 @@ packages: - encoding dev: false - /@react-native-community/cli-debugger-ui@11.3.10: - resolution: {integrity: sha512-kyitGV3RsjlXIioq9lsuawha2GUBPCTAyXV6EBlm3qlyF3dMniB3twEvz+fIOid/e1ZeucH3Tzy5G3qcP8yWoA==} + /@react-native-community/cli-debugger-ui@11.3.7: + resolution: {integrity: sha512-aVmKuPKHZENR8SrflkMurZqeyLwbKieHdOvaZCh1Nn/0UC5CxWcyST2DB2XQboZwsvr3/WXKJkSUO+SZ1J9qTQ==} dependencies: serve-static: 1.15.0 transitivePeerDependencies: - supports-color dev: false - /@react-native-community/cli-doctor@11.3.10: - resolution: {integrity: sha512-DpMsfCWKZ15L9nFK/SyDvpl5v6MjV+arMHMC1i8kR+DOmf2xWmp/pgMywKk0/u50yGB9GwxBHt3i/S/IMK5Ylg==} + /@react-native-community/cli-doctor@11.3.7: + resolution: {integrity: sha512-YEHUqWISOHnsl5+NM14KHelKh68Sr5/HeEZvvNdIcvcKtZic3FU7Xd1WcbNdo3gCq5JvzGFfufx02Tabh5zmrg==} dependencies: - '@react-native-community/cli-config': 11.3.10 - '@react-native-community/cli-platform-android': 11.3.10 - '@react-native-community/cli-platform-ios': 11.3.10 - '@react-native-community/cli-tools': 11.3.10 + '@react-native-community/cli-config': 11.3.7 + '@react-native-community/cli-platform-android': 11.3.7 + '@react-native-community/cli-platform-ios': 11.3.7 + '@react-native-community/cli-tools': 11.3.7 chalk: 4.1.2 command-exists: 1.2.9 envinfo: 7.11.0 @@ -4964,11 +4879,11 @@ packages: - encoding dev: false - /@react-native-community/cli-hermes@11.3.10: - resolution: {integrity: sha512-vqINuzAlcHS9ImNwJtT43N7kfBQ7ro9A8O1Gpc5TQ0A8V36yGG8eoCHeauayklVVgMZpZL6f6mcoLLr9IOgBZQ==} + /@react-native-community/cli-hermes@11.3.7: + resolution: {integrity: sha512-chkKd8n/xeZkinRvtH6QcYA8rjNOKU3S3Lw/3Psxgx+hAYV0Gyk95qJHTalx7iu+PwjOOqqvCkJo5jCkYLkoqw==} dependencies: - '@react-native-community/cli-platform-android': 11.3.10 - '@react-native-community/cli-tools': 11.3.10 + '@react-native-community/cli-platform-android': 11.3.7 + '@react-native-community/cli-tools': 11.3.7 chalk: 4.1.2 hermes-profile-transformer: 0.0.6 ip: 1.1.8 @@ -4976,10 +4891,10 @@ packages: - encoding dev: false - /@react-native-community/cli-platform-android@11.3.10: - resolution: {integrity: sha512-RGu9KuDIXnrcNkacSHj5ETTQtp/D/835L6veE2jMigO21p//gnKAjw3AVLCysGr8YXYfThF8OSOALrwNc94puQ==} + /@react-native-community/cli-platform-android@11.3.7: + resolution: {integrity: sha512-WGtXI/Rm178UQb8bu1TAeFC/RJvYGnbHpULXvE20GkmeJ1HIrMjkagyk6kkY3Ej25JAP2R878gv+TJ/XiRhaEg==} dependencies: - '@react-native-community/cli-tools': 11.3.10 + '@react-native-community/cli-tools': 11.3.7 chalk: 4.1.2 execa: 5.1.1 glob: 7.2.3 @@ -4988,10 +4903,10 @@ packages: - encoding dev: false - /@react-native-community/cli-platform-ios@11.3.10: - resolution: {integrity: sha512-JjduMrBM567/j4Hvjsff77dGSLMA0+p9rr0nShlgnKPcc+0J4TDy0hgWpUceM7OG00AdDjpetAPupz0kkAh4cQ==} + /@react-native-community/cli-platform-ios@11.3.7: + resolution: {integrity: sha512-Z/8rseBput49EldX7MogvN6zJlWzZ/4M97s2P+zjS09ZoBU7I0eOKLi0N9wx+95FNBvGQQ/0P62bB9UaFQH2jw==} dependencies: - '@react-native-community/cli-tools': 11.3.10 + '@react-native-community/cli-tools': 11.3.7 chalk: 4.1.2 execa: 5.1.1 fast-xml-parser: 4.3.2 @@ -5001,11 +4916,11 @@ packages: - encoding dev: false - /@react-native-community/cli-plugin-metro@11.3.10(@babel/core@7.23.3): - resolution: {integrity: sha512-ZYAc5Hc+QVqJgj1XFbpKnIPbSJ9xKcBnfQrRhR+jFyt2DWx85u4bbzY1GSVc/USs0UbSUXv4dqPbnmOJz52EYQ==} + /@react-native-community/cli-plugin-metro@11.3.7(@babel/core@7.23.3): + resolution: {integrity: sha512-0WhgoBVGF1f9jXcuagQmtxpwpfP+2LbLZH4qMyo6OtYLWLG13n2uRep+8tdGzfNzl1bIuUTeE9yZSAdnf9LfYQ==} dependencies: - '@react-native-community/cli-server-api': 11.3.10 - '@react-native-community/cli-tools': 11.3.10 + '@react-native-community/cli-server-api': 11.3.7 + '@react-native-community/cli-tools': 11.3.7 chalk: 4.1.2 execa: 5.1.1 metro: 0.76.8 @@ -5023,11 +4938,11 @@ packages: - utf-8-validate dev: false - /@react-native-community/cli-server-api@11.3.10: - resolution: {integrity: sha512-WEwHWIpqx3gA6Da+lrmq8+z78E1XbxxjBlvHAXevhjJj42N4SO417eZiiUVrFzEFVVJSUee9n9aRa0kUR+0/2w==} + /@react-native-community/cli-server-api@11.3.7: + resolution: {integrity: sha512-yoFyGdvR3HxCnU6i9vFqKmmSqFzCbnFSnJ29a+5dppgPRetN+d//O8ard/YHqHzToFnXutAFf2neONn23qcJAg==} dependencies: - '@react-native-community/cli-debugger-ui': 11.3.10 - '@react-native-community/cli-tools': 11.3.10 + '@react-native-community/cli-debugger-ui': 11.3.7 + '@react-native-community/cli-tools': 11.3.7 compression: 1.7.4 connect: 3.7.0 errorhandler: 1.5.1 @@ -5042,8 +4957,8 @@ packages: - utf-8-validate dev: false - /@react-native-community/cli-tools@11.3.10: - resolution: {integrity: sha512-4kCuCwVcGagSrNg9vxMNVhynwpByuC/J5UnKGEet3HuqmoDhQW15m18fJXiehA8J+u9WBvHduefy9nZxO0C06Q==} + /@react-native-community/cli-tools@11.3.7: + resolution: {integrity: sha512-peyhP4TV6Ps1hk+MBHTFaIR1eI3u+OfGBvr5r0wPwo3FAJvldRinMgcB/TcCcOBXVORu7ba1XYjkubPeYcqAyA==} dependencies: appdirsjs: 1.2.7 chalk: 4.1.2 @@ -5058,26 +4973,26 @@ packages: - encoding dev: false - /@react-native-community/cli-types@11.3.10: - resolution: {integrity: sha512-0FHK/JE7bTn0x1y8Lk5m3RISDHIBQqWLltO2Mf7YQ6cAeKs8iNOJOeKaHJEY+ohjsOyCziw+XSC4cY57dQrwNA==} + /@react-native-community/cli-types@11.3.7: + resolution: {integrity: sha512-OhSr/TiDQkXjL5YOs8+hvGSB+HltLn5ZI0+A3DCiMsjUgTTsYh+Z63OtyMpNjrdCEFcg0MpfdU2uxstCS6Dc5g==} dependencies: joi: 17.11.0 dev: false - /@react-native-community/cli@11.3.10(@babel/core@7.23.3): - resolution: {integrity: sha512-bIx0t5s9ewH1PlcEcuQUD+UnVrCjPGAfjhVR5Gew565X60nE+GTIHRn70nMv9G4he/amBF+Z+vf5t8SNZEWMwg==} + /@react-native-community/cli@11.3.7(@babel/core@7.23.3): + resolution: {integrity: sha512-Ou8eDlF+yh2rzXeCTpMPYJ2fuqsusNOhmpYPYNQJQ2h6PvaF30kPomflgRILems+EBBuggRtcT+I+1YH4o/q6w==} engines: {node: '>=16'} hasBin: true dependencies: - '@react-native-community/cli-clean': 11.3.10 - '@react-native-community/cli-config': 11.3.10 - '@react-native-community/cli-debugger-ui': 11.3.10 - '@react-native-community/cli-doctor': 11.3.10 - '@react-native-community/cli-hermes': 11.3.10 - '@react-native-community/cli-plugin-metro': 11.3.10(@babel/core@7.23.3) - '@react-native-community/cli-server-api': 11.3.10 - '@react-native-community/cli-tools': 11.3.10 - '@react-native-community/cli-types': 11.3.10 + '@react-native-community/cli-clean': 11.3.7 + '@react-native-community/cli-config': 11.3.7 + '@react-native-community/cli-debugger-ui': 11.3.7 + '@react-native-community/cli-doctor': 11.3.7 + '@react-native-community/cli-hermes': 11.3.7 + '@react-native-community/cli-plugin-metro': 11.3.7(@babel/core@7.23.3) + '@react-native-community/cli-server-api': 11.3.7 + '@react-native-community/cli-tools': 11.3.7 + '@react-native-community/cli-types': 11.3.7 chalk: 4.1.2 commander: 9.5.0 execa: 5.1.1 @@ -5127,17 +5042,17 @@ packages: resolution: {integrity: sha512-285lfdqSXaqKuBbbtP9qL2tDrfxdOFtIMvkKadtleRQkdOxx+uzGvFr82KHmc/sSiMtfXGp7JnFYWVh4sFl7Yw==} dev: false - /@react-native/virtualized-lists@0.72.8(react-native@0.72.7): + /@react-native/virtualized-lists@0.72.8(react-native@0.72.6): resolution: {integrity: sha512-J3Q4Bkuo99k7mu+jPS9gSUSgq+lLRSI/+ahXNwV92XgJ/8UgOTxu2LPwhJnBk/sQKxq7E8WkZBnBiozukQMqrw==} peerDependencies: react-native: '*' dependencies: invariant: 2.2.4 nullthrows: 1.1.1 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) dev: false - /@react-navigation/bottom-tabs@6.5.11(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native-screens@3.22.1)(react-native@0.72.7)(react@18.2.0): + /@react-navigation/bottom-tabs@6.5.11(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native-screens@3.22.1)(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-CBN/NOdxnMvmjw+AJQI1kltOYaClTZmGec5pQ3ZNTPX86ytbIOylDIITKMfTgHZcIEFQDymx1SHeS++PIL3Szw==} peerDependencies: '@react-navigation/native': ^6.0.0 @@ -5146,13 +5061,13 @@ packages: react-native-safe-area-context: '>= 3.0.0' react-native-screens: '>= 3.0.0' dependencies: - '@react-navigation/elements': 1.3.21(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native@0.72.7)(react@18.2.0) - '@react-navigation/native': 6.1.9(react-native@0.72.7)(react@18.2.0) + '@react-navigation/elements': 1.3.21(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native@0.72.6)(react@18.2.0) + '@react-navigation/native': 6.1.9(react-native@0.72.6)(react@18.2.0) color: 4.2.3 react: 18.2.0 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) - react-native-safe-area-context: 4.6.3(react-native@0.72.7)(react@18.2.0) - react-native-screens: 3.22.1(react-native@0.72.7)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native-safe-area-context: 4.6.3(react-native@0.72.6)(react@18.2.0) + react-native-screens: 3.22.1(react-native@0.72.6)(react@18.2.0) warn-once: 0.1.1 dev: false @@ -5170,7 +5085,7 @@ packages: use-latest-callback: 0.1.9(react@18.2.0) dev: false - /@react-navigation/elements@1.3.21(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native@0.72.7)(react@18.2.0): + /@react-navigation/elements@1.3.21(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-eyS2C6McNR8ihUoYfc166O1D8VYVh9KIl0UQPI8/ZJVsStlfSTgeEEh+WXge6+7SFPnZ4ewzEJdSAHH+jzcEfg==} peerDependencies: '@react-navigation/native': ^6.0.0 @@ -5178,13 +5093,13 @@ packages: react-native: '*' react-native-safe-area-context: '>= 3.0.0' dependencies: - '@react-navigation/native': 6.1.9(react-native@0.72.7)(react@18.2.0) + '@react-navigation/native': 6.1.9(react-native@0.72.6)(react@18.2.0) react: 18.2.0 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) - react-native-safe-area-context: 4.6.3(react-native@0.72.7)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native-safe-area-context: 4.6.3(react-native@0.72.6)(react@18.2.0) dev: false - /@react-navigation/native-stack@6.9.17(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native-screens@3.22.1)(react-native@0.72.7)(react@18.2.0): + /@react-navigation/native-stack@6.9.17(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native-screens@3.22.1)(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-X8p8aS7JptQq7uZZNFEvfEcPf6tlK4PyVwYDdryRbG98B4bh2wFQYMThxvqa+FGEN7USEuHdv2mF0GhFKfX0ew==} peerDependencies: '@react-navigation/native': ^6.0.0 @@ -5193,16 +5108,16 @@ packages: react-native-safe-area-context: '>= 3.0.0' react-native-screens: '>= 3.0.0' dependencies: - '@react-navigation/elements': 1.3.21(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native@0.72.7)(react@18.2.0) - '@react-navigation/native': 6.1.9(react-native@0.72.7)(react@18.2.0) + '@react-navigation/elements': 1.3.21(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native@0.72.6)(react@18.2.0) + '@react-navigation/native': 6.1.9(react-native@0.72.6)(react@18.2.0) react: 18.2.0 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) - react-native-safe-area-context: 4.6.3(react-native@0.72.7)(react@18.2.0) - react-native-screens: 3.22.1(react-native@0.72.7)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native-safe-area-context: 4.6.3(react-native@0.72.6)(react@18.2.0) + react-native-screens: 3.22.1(react-native@0.72.6)(react@18.2.0) warn-once: 0.1.1 dev: false - /@react-navigation/native@6.1.9(react-native@0.72.7)(react@18.2.0): + /@react-navigation/native@6.1.9(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-AMuJDpwXE7UlfyhIXaUCCynXmv69Kb8NzKgKJO7v0k0L+u6xUTbt6xvshmJ79vsvaFyaEH9Jg5FMzek5/S5qNw==} peerDependencies: react: '*' @@ -5213,7 +5128,7 @@ packages: fast-deep-equal: 3.1.3 nanoid: 3.3.7 react: 18.2.0 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) dev: false /@react-navigation/routers@6.1.9: @@ -5467,7 +5382,7 @@ packages: join-component: 1.1.0 dev: false - /@shopify/flash-list@1.4.3(@babel/runtime@7.23.4)(react-native@0.72.7)(react@18.2.0): + /@shopify/flash-list@1.4.3(@babel/runtime@7.23.4)(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-jtIReAbwWzYBV0dQ6Io9wBX+pD0C4qQFMrb5/fkEvX8PYDgBl5KRYvpfr9WLLj8CV2Jsn1X0mYOsB+ysWrI/8g==} peerDependencies: '@babel/runtime': '*' @@ -5476,8 +5391,8 @@ packages: dependencies: '@babel/runtime': 7.23.4 react: 18.2.0 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) - recyclerlistview: 4.2.0(react-native@0.72.7)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + recyclerlistview: 4.2.0(react-native@0.72.6)(react@18.2.0) tslib: 2.4.0 dev: false @@ -5565,7 +5480,7 @@ packages: resolution: {integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==} dev: false - /@tanstack/react-query@4.36.1(react-dom@18.2.0)(react-native@0.72.7)(react@18.2.0): + /@tanstack/react-query@4.36.1(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -5580,7 +5495,7 @@ packages: '@tanstack/query-core': 4.36.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) use-sync-external-store: 1.2.0(react@18.2.0) dev: false @@ -5624,7 +5539,7 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: - '@tanstack/react-query': 4.36.1(react-dom@18.2.0)(react-native@0.72.7)(react@18.2.0) + '@tanstack/react-query': 4.36.1(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0) '@trpc/client': 10.44.1(@trpc/server@10.44.1) '@trpc/react-query': 10.44.1(@tanstack/react-query@4.36.1)(@trpc/client@10.44.1)(@trpc/server@10.44.1)(react-dom@18.2.0)(react@18.2.0) '@trpc/server': 10.44.1 @@ -5643,7 +5558,7 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: - '@tanstack/react-query': 4.36.1(react-dom@18.2.0)(react-native@0.72.7)(react@18.2.0) + '@tanstack/react-query': 4.36.1(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0) '@trpc/client': 10.44.1(@trpc/server@10.44.1) '@trpc/server': 10.44.1 react: 18.2.0 @@ -6223,6 +6138,11 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: false + /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -6240,6 +6160,11 @@ packages: engines: {node: '>=10'} dev: false + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: false + /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -6641,6 +6566,10 @@ packages: /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + /base-64@0.1.0: + resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==} + dev: false + /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -7428,6 +7357,10 @@ packages: resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} dev: false + /crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + dev: false + /crypto-random-string@1.0.0: resolution: {integrity: sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg==} engines: {node: '>=4'} @@ -7667,8 +7600,16 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - /deprecated-react-native-prop-types@4.2.3: - resolution: {integrity: sha512-2rLTiMKidIFFYpIVM69UnQKngLqQfL6I11Ch8wGSBftS18FUXda+o2we2950X+1dmbgps28niI3qwyH4eX3Z1g==} + /deprecated-react-native-prop-types@2.3.0: + resolution: {integrity: sha512-pWD0voFtNYxrVqvBMYf5gq3NA2GCpfodS1yNynTPc93AYA/KEMGeWDqqeUB6R2Z9ZofVhks2aeJXiuQqKNpesA==} + dependencies: + '@react-native/normalize-color': 2.1.0 + invariant: 2.2.4 + prop-types: 15.8.1 + dev: false + + /deprecated-react-native-prop-types@4.1.0: + resolution: {integrity: sha512-WfepZHmRbbdTvhcolb8aOKEvQdcmTMn5tKLbqbXmkBvjFjRVWAYqsXk/DBsV8TZxws8SdGHLuHaJrHSQUPRdfw==} dependencies: '@react-native/normalize-colors': 0.72.0 invariant: 2.2.4 @@ -7902,6 +7843,10 @@ packages: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} dev: true + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: false + /ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -8593,7 +8538,7 @@ packages: fontfaceobserver: 2.3.0 dev: false - /expo-head@0.0.18(expo@49.0.21)(react-dom@18.2.0)(react-native@0.72.7)(react@18.2.0): + /expo-head@0.0.18(expo@49.0.21)(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-GaTAUiPf30GgoUTJEDJI0UdDUlLdfbFffU6NerIMwklSCFXJ1f9Nr8XG5qPEwm71ri/dssbToaMorIaXxtIc7Q==} peerDependencies: expo: '*' @@ -8603,7 +8548,7 @@ packages: expo: 49.0.21(@babel/core@7.23.3) react: 18.2.0 react-helmet-async: 1.3.0(react-dom@18.2.0)(react@18.2.0) - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) transitivePeerDependencies: - react-dom dev: false @@ -8650,7 +8595,7 @@ packages: invariant: 2.2.4 dev: false - /expo-router@2.0.12(expo-constants@14.4.2)(expo-linking@5.0.2)(expo-modules-autolinking@1.5.1)(expo-status-bar@1.7.1)(expo@49.0.21)(metro@0.76.8)(react-dom@18.2.0)(react-native-gesture-handler@2.12.1)(react-native-reanimated@3.3.0)(react-native-safe-area-context@4.6.3)(react-native-screens@3.22.1)(react-native@0.72.7)(react@18.2.0): + /expo-router@2.0.12(expo-constants@14.4.2)(expo-linking@5.0.2)(expo-modules-autolinking@1.5.1)(expo-status-bar@1.6.0)(expo@49.0.21)(metro@0.76.8)(react-dom@18.2.0)(react-native-gesture-handler@2.12.1)(react-native-reanimated@3.3.0)(react-native-safe-area-context@4.6.3)(react-native-screens@3.22.1)(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-4Z4hUE3H+jCjpbkoHD2da6osjnkSeoE882B8ynQTOZWSeKiLj8itM4tM0Dhctjo6OGuIV7A3fkqj843Yb6aHgQ==} peerDependencies: '@react-navigation/drawer': ^6.5.8 @@ -8672,25 +8617,25 @@ packages: react-native-reanimated: optional: true dependencies: - '@bacons/react-views': 1.1.3(react-native@0.72.7) - '@expo/metro-runtime': 2.2.14(react-native@0.72.7) + '@bacons/react-views': 1.1.3(react-native@0.72.6) + '@expo/metro-runtime': 2.2.14(react-native@0.72.6) '@radix-ui/react-slot': 1.0.1(react@18.2.0) - '@react-navigation/bottom-tabs': 6.5.11(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native-screens@3.22.1)(react-native@0.72.7)(react@18.2.0) - '@react-navigation/native': 6.1.9(react-native@0.72.7)(react@18.2.0) - '@react-navigation/native-stack': 6.9.17(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native-screens@3.22.1)(react-native@0.72.7)(react@18.2.0) + '@react-navigation/bottom-tabs': 6.5.11(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native-screens@3.22.1)(react-native@0.72.6)(react@18.2.0) + '@react-navigation/native': 6.1.9(react-native@0.72.6)(react@18.2.0) + '@react-navigation/native-stack': 6.9.17(@react-navigation/native@6.1.9)(react-native-safe-area-context@4.6.3)(react-native-screens@3.22.1)(react-native@0.72.6)(react@18.2.0) expo: 49.0.21(@babel/core@7.23.3) expo-constants: 14.4.2(expo@49.0.21) - expo-head: 0.0.18(expo@49.0.21)(react-dom@18.2.0)(react-native@0.72.7)(react@18.2.0) + expo-head: 0.0.18(expo@49.0.21)(react-dom@18.2.0)(react-native@0.72.6)(react@18.2.0) expo-linking: 5.0.2(expo@49.0.21) expo-splash-screen: 0.20.5(expo-modules-autolinking@1.5.1)(expo@49.0.21) - expo-status-bar: 1.7.1 + expo-status-bar: 1.6.0 metro: 0.76.8 query-string: 7.1.3 react-helmet-async: 1.3.0(react-dom@18.2.0)(react@18.2.0) - react-native-gesture-handler: 2.12.1(react-native@0.72.7)(react@18.2.0) - react-native-reanimated: 3.3.0(@babel/core@7.23.3)(@babel/plugin-proposal-nullish-coalescing-operator@7.18.6)(@babel/plugin-proposal-optional-chaining@7.21.0)(@babel/plugin-transform-arrow-functions@7.23.3)(@babel/plugin-transform-shorthand-properties@7.23.3)(@babel/plugin-transform-template-literals@7.23.3)(react-native@0.72.7)(react@18.2.0) - react-native-safe-area-context: 4.6.3(react-native@0.72.7)(react@18.2.0) - react-native-screens: 3.22.1(react-native@0.72.7)(react@18.2.0) + react-native-gesture-handler: 2.12.1(react-native@0.72.6)(react@18.2.0) + react-native-reanimated: 3.3.0(@babel/core@7.23.3)(@babel/plugin-proposal-nullish-coalescing-operator@7.18.6)(@babel/plugin-proposal-optional-chaining@7.21.0)(@babel/plugin-transform-arrow-functions@7.23.3)(@babel/plugin-transform-shorthand-properties@7.23.3)(@babel/plugin-transform-template-literals@7.23.3)(react-native@0.72.6)(react@18.2.0) + react-native-safe-area-context: 4.6.3(react-native@0.72.6)(react@18.2.0) + react-native-screens: 3.22.1(react-native@0.72.6)(react@18.2.0) schema-utils: 4.2.0 url: 0.11.3 transitivePeerDependencies: @@ -8702,8 +8647,8 @@ packages: - supports-color dev: false - /expo-secure-store@12.5.0(expo@49.0.21): - resolution: {integrity: sha512-Ow2ei+SB1w4zPLxmOg2PVPf2i45Eii0T9KUaER5iRLJAMcBWN4nyGCxzESOoN3OrvxVadP0hAn6RVE1G3gdD+Q==} + /expo-secure-store@12.3.1(expo@49.0.21): + resolution: {integrity: sha512-XLIgWDiIuiR0c+AA4NCWWibAMHCZUyRcy+lQBU49U6rvG+xmd3YrBJfQjfqAPyLroEqnLPGTWUX57GyRsfDOQw==} peerDependencies: expo: '*' dependencies: @@ -8723,25 +8668,12 @@ packages: - supports-color dev: false - /expo-splash-screen@0.22.0(expo-modules-autolinking@1.5.1)(expo@49.0.21): - resolution: {integrity: sha512-+iKesrtp8s3IQDXPgCwI6PZzQwhSGR/LLND1wOux8HrCmtveJQpomKBIdvwTb26GNKZiN1EtiQbnBZhn3EiKaA==} - peerDependencies: - expo: '*' - dependencies: - '@expo/prebuild-config': 6.4.0(expo-modules-autolinking@1.5.1) - expo: 49.0.21(@babel/core@7.23.3) - transitivePeerDependencies: - - encoding - - expo-modules-autolinking - - supports-color - dev: false - - /expo-status-bar@1.7.1: - resolution: {integrity: sha512-Wkm9uCmuQQBSU+l/AekWAQ1d0FYw560yL116+OAKJDyKBOUpPURoWkFbabM1EDxv+5scTuSThr/CvsA0nsSCow==} + /expo-status-bar@1.6.0: + resolution: {integrity: sha512-e//Oi2WPdomMlMDD3skE4+1ZarKCJ/suvcB4Jo/nO427niKug5oppcPNYO+csR6y3ZglGuypS+3pp/hJ+Xp6fQ==} dev: false - /expo-web-browser@12.5.0(expo@49.0.21): - resolution: {integrity: sha512-3uDzP19DqcEicLOB4ZH6pGWzxlCQ8mLHSmWMmfXEBhZjooUkHUrysbzkNvQQa24ijy3uoUybX4jW0xPss594kA==} + /expo-web-browser@12.3.2(expo@49.0.21): + resolution: {integrity: sha512-ohBf+vnRnGzlTleY8EQ2XQU0vRdRwqMJtKkzM9MZRPDOK5QyJYPJjpk6ixGhxdeoUG2Ogj0InvhhgX9NUn4jkg==} peerDependencies: expo: '*' dependencies: @@ -8992,6 +8924,14 @@ packages: is-callable: 1.2.7 dev: false + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + dev: false + /form-data@3.0.1: resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} engines: {node: '>= 6'} @@ -9242,6 +9182,18 @@ packages: /glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + /glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.3 + minipass: 5.0.0 + path-scurry: 1.10.1 + dev: false + /glob@6.0.4: resolution: {integrity: sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==} requiresBuild: true @@ -10089,6 +10041,15 @@ packages: set-function-name: 2.0.1 dev: false + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: false + /jest-environment-node@29.7.0: resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -10749,7 +10710,6 @@ packages: /lru-cache@10.1.0: resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} engines: {node: 14 || >=16.14} - dev: true /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -10773,7 +10733,7 @@ packages: es5-ext: 0.10.62 dev: true - /lucide-react-native@0.294.0(prop-types@15.8.1)(react-native-svg@14.1.0)(react-native@0.72.7)(react@18.2.0): + /lucide-react-native@0.294.0(prop-types@15.8.1)(react-native-svg@13.9.0)(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-0YB2WWtjQg/M2BEzHX1RJ4mHGPhET6MnS57QzfvMlIUXRTQkTU2f6zIv/TSFGQafCZOmP1Z/14fE54wvrAt4Dw==} peerDependencies: prop-types: ^15.7.2 @@ -10783,8 +10743,8 @@ packages: dependencies: prop-types: 15.8.1 react: 18.2.0 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) - react-native-svg: 14.1.0(react-native@0.72.7)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native-svg: 13.9.0(react-native@0.72.6)(react@18.2.0) dev: false /lucide-react@0.292.0(react@18.2.0): @@ -11311,7 +11271,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - dev: true /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -11424,13 +11383,13 @@ packages: resolution: {integrity: sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg==} dev: true - /nativewind@4.0.16(@babel/core@7.23.3)(react-native-reanimated@3.3.0)(react-native-safe-area-context@4.6.3)(react-native-svg@14.1.0)(react-native@0.72.7)(react@18.2.0)(tailwindcss@3.3.5): + /nativewind@4.0.16(@babel/core@7.23.3)(react-native-reanimated@3.3.0)(react-native-safe-area-context@4.6.3)(react-native-svg@13.9.0)(react-native@0.72.6)(react@18.2.0)(tailwindcss@3.3.5): resolution: {integrity: sha512-QeRt4kmubo9XvxYb0vJh74h09fpwM0A4sN4bWo40DMxrrIEbqpgjHeow8pMN1wN8lH5PHttTHyJqDNS/dGoxYQ==} engines: {node: '>=16'} peerDependencies: tailwindcss: '>3.3.0' dependencies: - react-native-css-interop: 0.0.16(@babel/core@7.23.3)(react-native-reanimated@3.3.0)(react-native-safe-area-context@4.6.3)(react-native-svg@14.1.0)(react-native@0.72.7)(react@18.2.0)(tailwindcss@3.3.5) + react-native-css-interop: 0.0.16(@babel/core@7.23.3)(react-native-reanimated@3.3.0)(react-native-safe-area-context@4.6.3)(react-native-svg@13.9.0)(react-native@0.72.6)(react@18.2.0)(tailwindcss@3.3.5) tailwindcss: 3.3.5 transitivePeerDependencies: - '@babel/core' @@ -12160,6 +12119,14 @@ packages: /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.1.0 + minipass: 5.0.0 + dev: false + /path-to-regexp@6.2.1: resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} dev: true @@ -12682,7 +12649,19 @@ packages: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: false - /react-native-css-interop@0.0.16(@babel/core@7.23.3)(react-native-reanimated@3.3.0)(react-native-safe-area-context@4.6.3)(react-native-svg@14.1.0)(react-native@0.72.7)(react@18.2.0)(tailwindcss@3.3.5): + /react-native-blob-util@0.19.6(react-native@0.72.6)(react@18.2.0): + resolution: {integrity: sha512-62yMJdgOMEu2Ir9V177FbvOYZskFu92XkT/hUWMMdu3G7UhqnetwtyVwqo4jfNrr+Y3Lp0gXYx60vxjMaov1pQ==} + peerDependencies: + react: '*' + react-native: '*' + dependencies: + base-64: 0.1.0 + glob: 10.3.10 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + dev: false + + /react-native-css-interop@0.0.16(@babel/core@7.23.3)(react-native-reanimated@3.3.0)(react-native-safe-area-context@4.6.3)(react-native-svg@13.9.0)(react-native@0.72.6)(react@18.2.0)(tailwindcss@3.3.5): resolution: {integrity: sha512-BSejbQyWfvEqaEqNzq7JGITLvoOgZVnRa0Vn1NqQcWm9OPfNE/OTk6k1AatIQ8SHHg0fRJ9v8+OGJYBR0j01PQ==} engines: {node: '>=16'} peerDependencies: @@ -12704,17 +12683,17 @@ packages: babel-plugin-tester: 11.0.4(@babel/core@7.23.3) lightningcss: 1.22.0 react: 18.2.0 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) - react-native-reanimated: 3.3.0(@babel/core@7.23.3)(@babel/plugin-proposal-nullish-coalescing-operator@7.18.6)(@babel/plugin-proposal-optional-chaining@7.21.0)(@babel/plugin-transform-arrow-functions@7.23.3)(@babel/plugin-transform-shorthand-properties@7.23.3)(@babel/plugin-transform-template-literals@7.23.3)(react-native@0.72.7)(react@18.2.0) - react-native-safe-area-context: 4.6.3(react-native@0.72.7)(react@18.2.0) - react-native-svg: 14.1.0(react-native@0.72.7)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native-reanimated: 3.3.0(@babel/core@7.23.3)(@babel/plugin-proposal-nullish-coalescing-operator@7.18.6)(@babel/plugin-proposal-optional-chaining@7.21.0)(@babel/plugin-transform-arrow-functions@7.23.3)(@babel/plugin-transform-shorthand-properties@7.23.3)(@babel/plugin-transform-template-literals@7.23.3)(react-native@0.72.6)(react@18.2.0) + react-native-safe-area-context: 4.6.3(react-native@0.72.6)(react@18.2.0) + react-native-svg: 13.9.0(react-native@0.72.6)(react@18.2.0) tailwindcss: 3.3.5 transitivePeerDependencies: - '@babel/core' - supports-color dev: false - /react-native-gesture-handler@2.12.1(react-native@0.72.7)(react@18.2.0): + /react-native-gesture-handler@2.12.1(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-deqh36bw82CFUV9EC4tTo2PP1i9HfCOORGS3Zmv71UYhEZEHkzZv18IZNPB+2Awzj45vLIidZxGYGFxHlDSQ5A==} peerDependencies: react: '*' @@ -12726,10 +12705,24 @@ packages: lodash: 4.17.21 prop-types: 15.8.1 react: 18.2.0 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + dev: false + + /react-native-pdf@6.7.3(react-native-blob-util@0.19.6)(react-native@0.72.6)(react@18.2.0): + resolution: {integrity: sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==} + peerDependencies: + react: '*' + react-native: '*' + react-native-blob-util: '>=0.13.7' + dependencies: + crypto-js: 4.2.0 + deprecated-react-native-prop-types: 2.3.0 + react: 18.2.0 + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native-blob-util: 0.19.6(react-native@0.72.6)(react@18.2.0) dev: false - /react-native-reanimated@3.3.0(@babel/core@7.23.3)(@babel/plugin-proposal-nullish-coalescing-operator@7.18.6)(@babel/plugin-proposal-optional-chaining@7.21.0)(@babel/plugin-transform-arrow-functions@7.23.3)(@babel/plugin-transform-shorthand-properties@7.23.3)(@babel/plugin-transform-template-literals@7.23.3)(react-native@0.72.7)(react@18.2.0): + /react-native-reanimated@3.3.0(@babel/core@7.23.3)(@babel/plugin-proposal-nullish-coalescing-operator@7.18.6)(@babel/plugin-proposal-optional-chaining@7.21.0)(@babel/plugin-transform-arrow-functions@7.23.3)(@babel/plugin-transform-shorthand-properties@7.23.3)(@babel/plugin-transform-template-literals@7.23.3)(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-LzfpPZ1qXBGy5BcUHqw3pBC0qSd22qXS3t8hWSbozXNrBkzMhhOrcILE/nEg/PHpNNp1xvGOW8NwpAMF006roQ==} peerDependencies: '@babel/core': ^7.0.0-0 @@ -12752,20 +12745,20 @@ packages: convert-source-map: 2.0.0 invariant: 2.2.4 react: 18.2.0 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) dev: false - /react-native-safe-area-context@4.6.3(react-native@0.72.7)(react@18.2.0): + /react-native-safe-area-context@4.6.3(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-3CeZM9HFXkuqiU9HqhOQp1yxhXw6q99axPWrT+VJkITd67gnPSU03+U27Xk2/cr9XrLUnakM07kj7H0hdPnFiQ==} peerDependencies: react: '*' react-native: '*' dependencies: react: 18.2.0 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) dev: false - /react-native-screens@3.22.1(react-native@0.72.7)(react@18.2.0): + /react-native-screens@3.22.1(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-ffzwUdVKf+iLqhWSzN5DXBm0s2w5sN0P+TaHHPAx42LT7+DT0g8PkHT1QDvxpR5vCEPSS1i3EswyVK4HCuhTYg==} peerDependencies: react: '*' @@ -12773,12 +12766,12 @@ packages: dependencies: react: 18.2.0 react-freeze: 1.0.3(react@18.2.0) - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) warn-once: 0.1.1 dev: false - /react-native-svg@14.1.0(react-native@0.72.7)(react@18.2.0): - resolution: {integrity: sha512-HeseElmEk+AXGwFZl3h56s0LtYD9HyGdrpg8yd9QM26X+d7kjETrRQ9vCjtxuT5dCZEIQ5uggU1dQhzasnsCWA==} + /react-native-svg@13.9.0(react-native@0.72.6)(react@18.2.0): + resolution: {integrity: sha512-Ey18POH0dA0ob/QiwCBVrxIiwflhYuw0P0hBlOHeY4J5cdbs8ngdKHeWC/Kt9+ryP6fNoEQ1PUgPYw2Bs/rp5Q==} peerDependencies: react: '*' react-native: '*' @@ -12786,30 +12779,30 @@ packages: css-select: 5.1.0 css-tree: 1.1.3 react: 18.2.0 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) dev: false - /react-native@0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0): - resolution: {integrity: sha512-dqVFojOO9rOvyFbbM3/v9/GJR355OSuBhEY4NQlMIRc2w0Xch5MT/2uPoq3+OvJ+5h7a8LFAco3fucSffG0FbA==} + /react-native@0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0): + resolution: {integrity: sha512-RafPY2gM7mcrFySS8TL8x+TIO3q7oAlHpzEmC7Im6pmXni6n1AuufGaVh0Narbr1daxstw7yW7T9BKW5dpVc2A==} engines: {node: '>=16'} hasBin: true peerDependencies: react: 18.2.0 dependencies: '@jest/create-cache-key-function': 29.7.0 - '@react-native-community/cli': 11.3.10(@babel/core@7.23.3) - '@react-native-community/cli-platform-android': 11.3.10 - '@react-native-community/cli-platform-ios': 11.3.10 + '@react-native-community/cli': 11.3.7(@babel/core@7.23.3) + '@react-native-community/cli-platform-android': 11.3.7 + '@react-native-community/cli-platform-ios': 11.3.7 '@react-native/assets-registry': 0.72.0 '@react-native/codegen': 0.72.7(@babel/preset-env@7.23.3) '@react-native/gradle-plugin': 0.72.11 '@react-native/js-polyfills': 0.72.1 '@react-native/normalize-colors': 0.72.0 - '@react-native/virtualized-lists': 0.72.8(react-native@0.72.7) + '@react-native/virtualized-lists': 0.72.8(react-native@0.72.6) abort-controller: 3.0.0 anser: 1.4.10 base64-js: 1.5.1 - deprecated-react-native-prop-types: 4.2.3 + deprecated-react-native-prop-types: 4.1.0 event-target-shim: 5.0.1 flow-enums-runtime: 0.0.5 invariant: 2.2.4 @@ -13000,7 +12993,7 @@ packages: tslib: 2.6.2 dev: false - /recyclerlistview@4.2.0(react-native@0.72.7)(react@18.2.0): + /recyclerlistview@4.2.0(react-native@0.72.6)(react@18.2.0): resolution: {integrity: sha512-uuBCi0c+ggqHKwrzPX4Z/mJOzsBbjZEAwGGmlwpD/sD7raXixdAbdJ6BTcAmuWG50Cg4ru9p12M94Njwhr/27A==} peerDependencies: react: '>= 15.2.1' @@ -13009,7 +13002,7 @@ packages: lodash.debounce: 4.0.8 prop-types: 15.8.1 react: 18.2.0 - react-native: 0.72.7(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) + react-native: 0.72.6(@babel/core@7.23.3)(@babel/preset-env@7.23.3)(react@18.2.0) ts-object-utils: 0.0.5 dev: false @@ -13558,6 +13551,11 @@ packages: /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: false + /simple-plist@1.3.1: resolution: {integrity: sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==} dependencies: @@ -13774,6 +13772,15 @@ packages: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: false + /string.prototype.matchall@4.0.10: resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} dependencies: @@ -13836,6 +13843,13 @@ packages: dependencies: ansi-regex: 5.0.1 + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: false + /strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -14968,6 +14982,15 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: false + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} From 2303cf95933112636141853344ac0e07960f40c7 Mon Sep 17 00:00:00 2001 From: ElektrikSpark Date: Mon, 11 Dec 2023 18:44:59 -0600 Subject: [PATCH 5/7] basic forms --- .../questionnaires/[questionnaireId].tsx | 33 +++++++++++++++++-- .../ui/health-record/question-item.tsx | 29 ++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 apps/expo/src/components/ui/health-record/question-item.tsx diff --git a/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/[questionnaireId].tsx b/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/[questionnaireId].tsx index 3b381c24..7f6da33a 100644 --- a/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/[questionnaireId].tsx +++ b/apps/expo/src/app/portal/(tabs)/health-record/forms/questionnaires/[questionnaireId].tsx @@ -1,6 +1,8 @@ import { Text, View } from "react-native"; import { Stack, useLocalSearchParams } from "expo-router"; +import { FlashList } from "@shopify/flash-list"; +import QuestionItem from "~/components/ui/health-record/question-item"; import { ChatRightHeaderClose, MessagesLeftHeaderBack, @@ -26,7 +28,7 @@ export default function QuesitonnairePage() { } return ( - + , }} /> - Questionnaire Page - {questionnaireId} + + {data?.questionnaire ?? "unknown questionnaire"} + + {data?.status ?? "unknown"} + {data?.authored ?? "unknown"} + ( + + )} + estimatedItemSize={100} + keyExtractor={(item, index) => index.toString()} + contentContainerStyle={{ + paddingBottom: 16, + // paddingTop: 16, + // paddingHorizontal: 16, + }} + /> ); } diff --git a/apps/expo/src/components/ui/health-record/question-item.tsx b/apps/expo/src/components/ui/health-record/question-item.tsx new file mode 100644 index 00000000..6e236ece --- /dev/null +++ b/apps/expo/src/components/ui/health-record/question-item.tsx @@ -0,0 +1,29 @@ +import { Text, View } from "react-native"; +import { clsx } from "clsx"; + +export default function QuestionItem({ + text, + answer, + first, + last, +}: { + text: string; + answer: string; + first?: boolean; + last?: boolean; +}) { + return ( + + + {text} + {answer} + + + ); +} From a7460511e95d85cb1224464df22c0471e4ac1e8b Mon Sep 17 00:00:00 2001 From: ElektrikSpark Date: Tue, 12 Dec 2023 01:34:37 -0600 Subject: [PATCH 6/7] basic health record done --- .../portal/(tabs)/health-record/_layout.tsx | 2 +- .../(tabs)/health-record/test-results.tsx | 9 -- .../health-record/test-results/[testId].tsx | 100 ++++++++++++++++++ .../health-record/test-results/_layout.tsx | 30 ++++++ .../health-record/test-results/index.tsx | 78 ++++++++++++++ .../ui/health-record/result-item.tsx | 44 ++++++++ .../components/ui/health-record/test-item.tsx | 44 ++++++++ packages/api/src/root.ts | 4 + packages/api/src/router/diagnostic-report.ts | 39 +++++++ packages/api/src/router/observation.ts | 36 +++++++ .../api/src/validators/diagnostic-report.ts | 10 +- 11 files changed, 384 insertions(+), 12 deletions(-) delete mode 100644 apps/expo/src/app/portal/(tabs)/health-record/test-results.tsx create mode 100644 apps/expo/src/app/portal/(tabs)/health-record/test-results/[testId].tsx create mode 100644 apps/expo/src/app/portal/(tabs)/health-record/test-results/_layout.tsx create mode 100644 apps/expo/src/app/portal/(tabs)/health-record/test-results/index.tsx create mode 100644 apps/expo/src/components/ui/health-record/result-item.tsx create mode 100644 apps/expo/src/components/ui/health-record/test-item.tsx create mode 100644 packages/api/src/router/diagnostic-report.ts create mode 100644 packages/api/src/router/observation.ts diff --git a/apps/expo/src/app/portal/(tabs)/health-record/_layout.tsx b/apps/expo/src/app/portal/(tabs)/health-record/_layout.tsx index 65f0ed9e..d6564fe9 100644 --- a/apps/expo/src/app/portal/(tabs)/health-record/_layout.tsx +++ b/apps/expo/src/app/portal/(tabs)/health-record/_layout.tsx @@ -29,7 +29,7 @@ export default function HealthRecordLayout() { headerStyle: { backgroundColor: "#fff", }, - headerLeft: () => , + headerShown: false, }} /> - TestResults - - ); -} diff --git a/apps/expo/src/app/portal/(tabs)/health-record/test-results/[testId].tsx b/apps/expo/src/app/portal/(tabs)/health-record/test-results/[testId].tsx new file mode 100644 index 00000000..5660c706 --- /dev/null +++ b/apps/expo/src/app/portal/(tabs)/health-record/test-results/[testId].tsx @@ -0,0 +1,100 @@ +import { Text, View } from "react-native"; +import { Stack, useLocalSearchParams } from "expo-router"; +import { FlashList } from "@shopify/flash-list"; + +import ResultItem from "~/components/ui/health-record/result-item"; +import { + ChatRightHeaderClose, + MessagesLeftHeaderBack, +} from "~/components/ui/messages-header"; +import { api } from "~/utils/api"; + +export default function TestPage() { + const { testId, type } = useLocalSearchParams<{ + testId: string; + type: "Observation" | "DiagnosticReport"; + }>(); + + const diagnosticReportQuery = + api.diagnosticReport.getDiagnosticReport.useQuery( + { + id: testId, + }, + { + enabled: type === "DiagnosticReport", + }, + ); + + const observationQuery = api.observation.getObservation.useQuery( + { + id: testId, + }, + { + enabled: type === "Observation", + }, + ); + + const isLoading = + diagnosticReportQuery.isLoading || observationQuery.isLoading; + const isError = diagnosticReportQuery.isError || observationQuery.isError; + const error = diagnosticReportQuery.error ?? observationQuery.error; + const data = + type === "DiagnosticReport" + ? diagnosticReportQuery.data + : observationQuery.data; + + if (isLoading) { + return Loading...; + } + + if (isError) { + return Error: {error?.message}; + } + + const diagnosticReportItems = diagnosticReportQuery.data?.presentedForm; + const observationItems = observationQuery.data?.hasMember; + + return ( + + , + headerRight: () => , + }} + /> + + {data?.code?.coding?.[0]?.display ?? "Unknown Test"} + + Collected on {data?.effectiveDateTime ?? "Unknown Date"} + + {type === "DiagnosticReport" && diagnosticReportItems && ( + DiagnosticReport Result} + estimatedItemSize={100} + keyExtractor={(item, index) => index.toString()} + contentContainerStyle={{ + paddingBottom: 16, + // paddingTop: 16, + // paddingHorizontal: 16, + }} + /> + )} + + {type === "Observation" && observationItems && ( + ({ url: item.reference }))} // Transform to match expected shape + renderItem={({ item, index }) => Observation Result} + estimatedItemSize={100} + keyExtractor={(item, index) => index.toString()} + contentContainerStyle={{ + paddingBottom: 16, + // paddingTop: 16, + // paddingHorizontal: 16, + }} + /> + )} + + ); +} diff --git a/apps/expo/src/app/portal/(tabs)/health-record/test-results/_layout.tsx b/apps/expo/src/app/portal/(tabs)/health-record/test-results/_layout.tsx new file mode 100644 index 00000000..ee05a54d --- /dev/null +++ b/apps/expo/src/app/portal/(tabs)/health-record/test-results/_layout.tsx @@ -0,0 +1,30 @@ +import { Stack } from "expo-router"; + +import { LeftHeaderBack } from "~/components/ui/tabs-header"; + +export default function TestResultsLayout() { + return ( + + , + }} + /> + + + ); +} diff --git a/apps/expo/src/app/portal/(tabs)/health-record/test-results/index.tsx b/apps/expo/src/app/portal/(tabs)/health-record/test-results/index.tsx new file mode 100644 index 00000000..703b8fc0 --- /dev/null +++ b/apps/expo/src/app/portal/(tabs)/health-record/test-results/index.tsx @@ -0,0 +1,78 @@ +import { Text, View } from "react-native"; +import { useRouter } from "expo-router"; +import { FlashList } from "@shopify/flash-list"; +import { useAtom } from "jotai"; + +import TestItem from "~/components/ui/health-record/test-item"; +import { api } from "~/utils/api"; +import { patientAtom } from "../allergies"; + +export default function TestResultsPage() { + const [patientId] = useAtom(patientAtom); + const router = useRouter(); + + const { isLoading, isError, data, error } = + api.patientMedicalHistory.getPatientDiagnosticReports.useQuery({ + patientId, + }); + + const { + isLoading: isObsLoading, + isError: isObsError, + data: obsData, + error: obsError, + } = api.patientMedicalHistory.getPatientObservations.useQuery({ + patientId, + }); + + if (isLoading || isObsLoading) { + return Loading...; + } + + if (isError || isObsError) { + return Error: {error?.message ?? obsError?.message}; + } + + // Merge and tag the data + const mergedData = [ + ...(data?.entry?.map((item) => ({ ...item, type: "diagnosticReport" })) ?? + []), + ...(obsData?.entry?.map((item) => ({ ...item, type: "observation" })) ?? + []), + ]; + + return ( + + {data?.total > 0 ? ( + ( + + router.push({ + pathname: `/portal/(tabs)/health-record/test-results/${item.resource?.id}`, + params: { type: item.type }, + }) + } + first={index === 0} + last={index === mergedData.length - 1} + /> + )} + estimatedItemSize={100} + keyExtractor={(item, index) => index.toString()} + contentContainerStyle={{ + paddingBottom: 16, + // paddingTop: 16, + // paddingHorizontal: 16, + }} + /> + ) : ( + {`No test results found.`} + )} + + ); +} diff --git a/apps/expo/src/components/ui/health-record/result-item.tsx b/apps/expo/src/components/ui/health-record/result-item.tsx new file mode 100644 index 00000000..44bc92a8 --- /dev/null +++ b/apps/expo/src/components/ui/health-record/result-item.tsx @@ -0,0 +1,44 @@ +import { Text, View } from "react-native"; +import { clsx } from "clsx"; +import { ActivitySquare, FlaskConical } from "lucide-react-native"; + +export default function ResultItem({ + name, + authored, + issued, + type, + onPress, + first, + last, +}: { + name: string; + authored: string; + issued: string; + type: boolean; + onPress: () => void; + first?: boolean; + last?: boolean; +}) { + // Determine which icon to use based on the type + const Icon = type ? FlaskConical : ActivitySquare; + + return ( + + + + + {name} + Authored: {authored} + Issued: {issued} + + + + ); +} diff --git a/apps/expo/src/components/ui/health-record/test-item.tsx b/apps/expo/src/components/ui/health-record/test-item.tsx new file mode 100644 index 00000000..cf394bcc --- /dev/null +++ b/apps/expo/src/components/ui/health-record/test-item.tsx @@ -0,0 +1,44 @@ +import { Text, View } from "react-native"; +import { clsx } from "clsx"; +import { ActivitySquare, FlaskConical } from "lucide-react-native"; + +export default function TestItem({ + name, + authored, + issued, + type, + onPress, + first, + last, +}: { + name: string; + authored: string; + issued: string; + type: boolean; + onPress: () => void; + first?: boolean; + last?: boolean; +}) { + // Determine which icon to use based on the type + const Icon = type ? FlaskConical : ActivitySquare; + + return ( + + + + + {name} + Authored: {authored} + Issued: {issued} + + + + ); +} diff --git a/packages/api/src/root.ts b/packages/api/src/root.ts index db968de7..cf89f318 100644 --- a/packages/api/src/root.ts +++ b/packages/api/src/root.ts @@ -3,7 +3,9 @@ import { careTeamRouter } from "./router/care-team"; import { communicationRouter } from "./router/communication"; import { consentRouter } from "./router/consent"; import { coverageRouter } from "./router/coverage"; +import { diagnosticReportRouter } from "./router/diagnostic-report"; import { documentRouter } from "./router/document"; +import { observationRouter } from "./router/observation"; import { patientRouter } from "./router/patient"; import { patientMedicalHistoryRouter } from "./router/patient-medical-history"; import { paymentRouter } from "./router/payment"; @@ -32,6 +34,8 @@ export const appRouter = createTRPCRouter({ patientMedicalHistory: patientMedicalHistoryRouter, scheduling: schedulingRouter, payment: paymentRouter, + diagnosticReport: diagnosticReportRouter, + observation: observationRouter, }); // export type definition of API diff --git a/packages/api/src/router/diagnostic-report.ts b/packages/api/src/router/diagnostic-report.ts new file mode 100644 index 00000000..52961e9a --- /dev/null +++ b/packages/api/src/router/diagnostic-report.ts @@ -0,0 +1,39 @@ +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; + +import { get_ReadDiagnosticreport } from "../canvas/canvas-client"; +import { createTRPCRouter, protectedCanvasProcedure } from "../trpc"; + +export const diagnosticReportRouter = createTRPCRouter({ + getDiagnosticReport: protectedCanvasProcedure + .input(z.object({ id: z.string() })) + .query(async ({ ctx, input }) => { + const { api, canvasToken } = ctx; + const { id } = input; + + if (!canvasToken) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Canvas token is missing", + }); + } + + try { + const diagnosticreportData = await api.get( + "/DiagnosticReport/{diagnostic_report_id}", + { + path: { diagnostic_report_id: id }, + }, + ); + const validatedData = + get_ReadDiagnosticreport.response.parse(diagnosticreportData); + return validatedData; + } catch (error) { + // Handle any other errors + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "An error occurred while fetching diagnosticreport data", + }); + } + }), +}); diff --git a/packages/api/src/router/observation.ts b/packages/api/src/router/observation.ts new file mode 100644 index 00000000..1e80b190 --- /dev/null +++ b/packages/api/src/router/observation.ts @@ -0,0 +1,36 @@ +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; + +import { get_ReadObservation } from "../canvas/canvas-client"; +import { createTRPCRouter, protectedCanvasProcedure } from "../trpc"; + +export const observationRouter = createTRPCRouter({ + getObservation: protectedCanvasProcedure + .input(z.object({ id: z.string() })) + .query(async ({ ctx, input }) => { + const { api, canvasToken } = ctx; + const { id } = input; + + if (!canvasToken) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Canvas token is missing", + }); + } + + try { + const observationData = await api.get("/Observation/{observation_id}", { + path: { observation_id: id }, + }); + const validatedData = + get_ReadObservation.response.parse(observationData); + return validatedData; + } catch (error) { + // Handle any other errors + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "An error occurred while fetching observation data", + }); + } + }), +}); diff --git a/packages/api/src/validators/diagnostic-report.ts b/packages/api/src/validators/diagnostic-report.ts index e19368f7..a29c02dd 100644 --- a/packages/api/src/validators/diagnostic-report.ts +++ b/packages/api/src/validators/diagnostic-report.ts @@ -8,7 +8,7 @@ const linkSchema = z.object({ const codingSchema = z.object({ system: z.string(), code: z.string(), - display: z.string(), + display: z.string().optional(), }); const categorySchema = z.object({ @@ -24,16 +24,22 @@ const referenceSchema = z.object({ type: z.string(), }); +const presentedFormSchema = z.object({ + url: z.string(), +}); + export const diagnosticReportResourceSchema = z.object({ - resourceType: z.string(), + resourceType: z.literal("DiagnosticReport"), id: z.string(), status: z.string(), category: z.array(categorySchema).optional(), code: codeSchema, subject: referenceSchema, + encounter: referenceSchema.optional(), effectiveDateTime: z.string(), issued: z.string(), performer: z.array(referenceSchema).optional(), + presentedForm: z.array(presentedFormSchema).optional(), }); const entrySchema = z.object({ From 75d241c07578688f9fa347382d76a76242f3b7e0 Mon Sep 17 00:00:00 2001 From: ElektrikSpark Date: Tue, 12 Dec 2023 01:42:09 -0600 Subject: [PATCH 7/7] fix: validators imports --- .../onboarding/_components/checkbox-question.tsx | 2 +- .../(authenticated)/onboarding/_components/consent-form.tsx | 5 ++--- .../onboarding/_components/coverage-form.tsx | 4 ++-- .../onboarding/_components/questionnaire-form.tsx | 6 +++--- .../onboarding/_components/schedule-appointment.tsx | 2 +- .../(authenticated)/onboarding/_components/welcome-form.tsx | 4 ++-- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/nextjs/src/app/(authenticated)/onboarding/_components/checkbox-question.tsx b/apps/nextjs/src/app/(authenticated)/onboarding/_components/checkbox-question.tsx index 84da733d..e8567539 100644 --- a/apps/nextjs/src/app/(authenticated)/onboarding/_components/checkbox-question.tsx +++ b/apps/nextjs/src/app/(authenticated)/onboarding/_components/checkbox-question.tsx @@ -2,7 +2,7 @@ import type { UseFormReturn } from "react-hook-form"; -import type { ValueCoding } from "@acme/api/src/validators"; +import type { ValueCoding } from "@acme/api/src/validators/questionnaire-response"; import { Checkbox } from "@acme/ui/checkbox"; import { FormControl, diff --git a/apps/nextjs/src/app/(authenticated)/onboarding/_components/consent-form.tsx b/apps/nextjs/src/app/(authenticated)/onboarding/_components/consent-form.tsx index 1ae35044..c489565e 100644 --- a/apps/nextjs/src/app/(authenticated)/onboarding/_components/consent-form.tsx +++ b/apps/nextjs/src/app/(authenticated)/onboarding/_components/consent-form.tsx @@ -1,11 +1,10 @@ "use client"; -import { useRouter } from "next/navigation"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; -import { consentFormSchema } from "@acme/api/src/validators"; -import type { ConsentForm } from "@acme/api/src/validators"; +import { consentFormSchema } from "@acme/api/src/validators/forms"; +import type { ConsentForm } from "@acme/api/src/validators/forms"; import { Button } from "@acme/ui/button"; import { Checkbox } from "@acme/ui/checkbox"; import { diff --git a/apps/nextjs/src/app/(authenticated)/onboarding/_components/coverage-form.tsx b/apps/nextjs/src/app/(authenticated)/onboarding/_components/coverage-form.tsx index c34cacba..120621e3 100644 --- a/apps/nextjs/src/app/(authenticated)/onboarding/_components/coverage-form.tsx +++ b/apps/nextjs/src/app/(authenticated)/onboarding/_components/coverage-form.tsx @@ -3,8 +3,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; -import { coverageFormSchema } from "@acme/api/src/validators"; -import type { CoverageForm } from "@acme/api/src/validators"; +import { coverageFormSchema } from "@acme/api/src/validators/forms"; +import type { CoverageForm } from "@acme/api/src/validators/forms"; import { Button } from "@acme/ui/button"; import { Form, diff --git a/apps/nextjs/src/app/(authenticated)/onboarding/_components/questionnaire-form.tsx b/apps/nextjs/src/app/(authenticated)/onboarding/_components/questionnaire-form.tsx index 559b12ff..a780f2f0 100644 --- a/apps/nextjs/src/app/(authenticated)/onboarding/_components/questionnaire-form.tsx +++ b/apps/nextjs/src/app/(authenticated)/onboarding/_components/questionnaire-form.tsx @@ -1,12 +1,12 @@ "use client"; import { useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; import { z } from "zod"; import type { ZodSchema } from "zod"; -import { generateQuestionnaireSchema } from "@acme/api/src/validators"; -import type { QuestionItem, ValueCoding } from "@acme/api/src/validators"; +import { generateQuestionnaireSchema } from "@acme/api/src/validators/forms"; +import type { QuestionItem } from "@acme/api/src/validators/forms"; +import type { ValueCoding } from "@acme/api/src/validators/questionnaire-response"; import { Button } from "@acme/ui/button"; import { Form } from "@acme/ui/form"; import { useToast } from "@acme/ui/use-toast"; diff --git a/apps/nextjs/src/app/(authenticated)/onboarding/_components/schedule-appointment.tsx b/apps/nextjs/src/app/(authenticated)/onboarding/_components/schedule-appointment.tsx index 75bcba23..5d1597a6 100644 --- a/apps/nextjs/src/app/(authenticated)/onboarding/_components/schedule-appointment.tsx +++ b/apps/nextjs/src/app/(authenticated)/onboarding/_components/schedule-appointment.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/navigation"; import { atom, useAtom } from "jotai"; -import type { SlotResource } from "@acme/api/src/validators"; +import type { SlotResource } from "@acme/api/src/validators/slot"; import { Button } from "@acme/ui/button"; import { useToast } from "@acme/ui/use-toast"; diff --git a/apps/nextjs/src/app/(authenticated)/onboarding/_components/welcome-form.tsx b/apps/nextjs/src/app/(authenticated)/onboarding/_components/welcome-form.tsx index 8c832d10..3065d04e 100644 --- a/apps/nextjs/src/app/(authenticated)/onboarding/_components/welcome-form.tsx +++ b/apps/nextjs/src/app/(authenticated)/onboarding/_components/welcome-form.tsx @@ -2,8 +2,8 @@ import { useRouter } from "next/navigation"; -import type { NewPatient } from "@acme/api/src/validators"; -import { newPatientSchema } from "@acme/api/src/validators"; +import type { NewPatient } from "@acme/api/src/validators/forms"; +import { newPatientSchema } from "@acme/api/src/validators/forms"; import { Button } from "@acme/ui/button"; import { Form,