diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7a9dfa0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:8080", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/friendly-names.json b/friendly-names.json index 36cf7f3..0206d14 100644 --- a/friendly-names.json +++ b/friendly-names.json @@ -9,67 +9,78 @@ "LIST": [ { "FHIR": "AdministerMedication", - "FRIENDLY": "Give Medication" + "FRIENDLY": "Give Medication", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-administermedication" }, { "FHIR": "CollectInformation", - "FRIENDLY": "Ask for Info" + "FRIENDLY": "Ask for Info", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-collectinformationactivity" }, { "FHIR": "CommunicationRequest", - "FRIENDLY": "Talk to me" + "FRIENDLY": "Talk to me", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-communicationactivity" }, { "FHIR": "Computable", - "FRIENDLY": "Can go in computer" + "FRIENDLY": "Can go in computer", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-computableactivity" }, { "FHIR": "DispenseMedication", - "FRIENDLY": "Give Meds" + "FRIENDLY": "Give Meds", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-dispensemedicationactivity" }, { "FHIR": "DocumentMedication", - "FRIENDLY": "Note the meds" + "FRIENDLY": "Note the meds", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-documentmedicationactivity" }, { "FHIR": "Enrollment", - "FRIENDLY": "Start them on program" + "FRIENDLY": "Start them on program", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-enrollmentactivity" }, { "FHIR": "GenerateReport", - "FRIENDLY": "Make a Report" + "FRIENDLY": "Make a Report", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-generatereportactivity" }, { "FHIR": "ImmunizationRecommendation", - "FRIENDLY": "Recommend a Shot" + "FRIENDLY": "Recommend a Shot", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-immunizationactivity" }, { "FHIR": "MedicationRequest", - "FRIENDLY": "Write Prescription" + "FRIENDLY": "Write Prescription", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-medicationrequestactivity" }, { "FHIR": "ProposeDiagnosisTask", - "FRIENDLY": "Suggest Diagnosis" + "FRIENDLY": "Suggest Diagnosis", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-proposediagnosisactivity" }, { "FHIR": "RecordDetectedIssueTask", - "FRIENDLY": "Note Issue" + "FRIENDLY": "Note Issue", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-recorddetectedissueactivity" }, { "FHIR": "RecordInferenceTask", - "FRIENDLY": "Note Prognosis" + "FRIENDLY": "Note Prognosis", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-recordinferenceactivity" }, { "FHIR": "ReportFlagTask", - "FRIENDLY": "Notable item to flag" + "FRIENDLY": "Notable item to flag", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-reportflagactivity" }, { "FHIR": "ServiceRequest", - "FRIENDLY": "Request Help" - }, - { - "FHIR": "ServiceRequest", - "FRIENDLY": "Request Help" + "FRIENDLY": "Request Help", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-servicerequestactivity" } ] }, @@ -96,13 +107,14 @@ "SELF": { "FHIR": "Questionnaire", "FRIENDLY": "List of Questions", - "DEFAULT_PROFILE_URI": "NONE" + "DEFAULT_PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-shareablequestionnaire" }, "LIST": [ { "FHIR": "QuestionnaireExample", - "FRIENDLY": "An Example Questionnaire" + "FRIENDLY": "An Example Questionnaire", + "PROFILE_URI": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-shareablequestionnaire" } ] }, diff --git a/public/app.css b/public/app.css index 289c656..d59189e 100644 --- a/public/app.css +++ b/public/app.css @@ -299,7 +299,7 @@ span.fhir-element-value { .box { border:3px solid #2a6b92; padding:25px 10px 10px 10px; - border-radius:10px; + border-radius:0.25rem; } a { @@ -308,7 +308,7 @@ a { /* CARDS AND FOLDERS */ -.card { +/* .card { cursor:pointer; margin:0px 0px 15px 0px; border-radius:10px; @@ -326,15 +326,34 @@ a { .card-body { background-color:#88d1ff; +}*/ + +.bg-sage-white { + background-color: #ffffff; +} + +.text-sage-blue { + color: #2a6b92; +} + +.border-activitydefinition, .border-questionnaire { + border-width: medium; +} + +.border-activitydefinition { + border-color: #904c77; +} + +.border-questionnaire { + border-color: #ce6c47; } -.folder:hover .card{ +/* .folder:hover .card{ color: white; transition: .3s; -} +} */ .folder:hover .folder-type { - color: white; transform: translate(0px, -9px); transition: transform .3s; } @@ -347,6 +366,24 @@ a { height: 60px; } +.folder .delete { + position:absolute; + top:22px; + left:213px; + width:30px; + height:30px; + border-radius:30px; + border: 0px; + /* background-color:#25a5f5; */ + font-size:17px; + +} + +/* .folder .delete:hover { + color:red; + background-color: #296a90; +} */ + /* BUTTONS */ .navigate { @@ -488,23 +525,6 @@ a { transform: scale(0.9); transition: opacity 300ms, transform 300ms; } -button.delete { - position:absolute; - top:22px; - left:213px; - width:30px; - height:30px; - border-radius:30px; - border: 0px; - background-color:#25a5f5; - font-size:17px; - -} - -button.delete:hover { - color:red; - background-color: #296a90; -} /* SLIDE IN/OUT TRANSITIONS */ .view-from-right-enter { diff --git a/src/dialogs/cpg-dialog.jsx b/src/dialogs/cpg-dialog.jsx index bc84eb2..e028fe4 100644 --- a/src/dialogs/cpg-dialog.jsx +++ b/src/dialogs/cpg-dialog.jsx @@ -526,6 +526,20 @@ class CpgDialog extends React.Component { } renderTabs() { + if (this.props.basic) { + // The basic Tabs will eventually be the same as in the else clause, so this duplication is temporary + return ( + + + {this.renderNewCPGInput()} + + + ); + } return ( (fhirType[0] === fhirType[0].toUpperCase()); const isInfrastructureType = (fhirType: string): boolean => ["DomainResource", "Element", "BackboneElement"].includes(fhirType); -const linkPrefix = "http://hl7.org/fhir/"; +const linkPrefix = "http://hl7.org/fhir"; // Element names that will be skipped (will not appear in the "Add Element" dropdown) const unsupportedElements: string[] = []; @@ -525,7 +525,8 @@ export const isSupportedResource = function(data: any): data is SageSupportedFhi // checks if the SchemaNode uses a profile and returns its URI if so. // otherwise, it returns a default for that type or undefined if one doesn't exist (bad?) const getProfileOfSchemaDef = function(profiles: SimplifiedProfiles, schemaNode: SchemaDef, typeDef?: ElementDefinitionType) : string | undefined { - typeDef = typeDef || schemaNode.type[0]; + // console.log('getProfileOfSchemaDef', schemaNode, typeDef); + typeDef = typeDef ?? schemaNode.type[0]; if (typeDef.profile) { return typeDef.profile[0]; } @@ -724,7 +725,7 @@ export const walkNode = (profiles: SimplifiedProfiles, valueOfNode: any, profile // Check if a new profile should be used for this element const newProfile = getProfileOfSchemaDef(profiles, schema, schema.type[typeIdx]); const childSchemaPath = newProfile ? fhirType : trueSchemaPath; - const childProfile = newProfile || profileUri; + const childProfile = newProfile ?? profileUri; // TODO: Figure out which type of the array this node corresponds to const displayName = buildDisplayName(name, schema.sliceName); // if (isInfrastructureType(fhirType) && (schemaPath.length === 1)) { @@ -915,36 +916,27 @@ export const decorateFhirData = function(profiles: SimplifiedProfiles, resource: decorated.children = createChildrenFromJson(profiles, decorated, resource); decorated.children = decorated.children.sort((a, b) => a.index - b.index); - // console.log('end decoratefhirdata: ', decorated); + console.log('end decoratefhirdata: ', decorated); return decorated; }; const uvCode = "uv"; const cpgCode = "cpg"; -const ipsCode = "ips" +const ipsCode = "ips"; -export function makeLink (resource: { FHIR: any; FRIENDLY?: string; } - , type: { FHIR?: string; FRIENDLY?: string; ""?: any; }) { - return linkPrefix + uvCode + "/" + cpgCode + "/" + type.FHIR + "/" + cpgCode + "-" - + (resource.FHIR + "-" + type.FHIR).toLowerCase() -} - -export function makeProfile(resource: { FHIR: any; FRIENDLY?: string; } | string): string { +export function makeProfile(resource: FriendlyResourceListEntry | string): string { - var resourceAsString: string = "" if (typeof(resource) !== 'string') { - resourceAsString = resource.FHIR; - } else { - resourceAsString = resource + return resource.PROFILE_URI; } - return linkPrefix + uvCode + "/" + cpgCode + "/" + STRUCTURE_DEFINITION + "/" + cpgCode + "-" - + resourceAsString.toLowerCase() + return linkPrefix + "/" + uvCode + "/" + cpgCode + "/" + STRUCTURE_DEFINITION + "/" + cpgCode + "-" + + resource.toLowerCase() } -export function makeValueSetURL(resource: { FHIR: any; FRIENDLY?: string; }): string { +export function makeValueSetURL(resource: FriendlyResourceListEntry): string { - return linkPrefix+ uvCode + "/" + ipsCode + "/" + VALUE_SET + "/" + (resource.FHIR).toLowerCase() + return linkPrefix + "/" + uvCode + "/" + ipsCode + "/" + VALUE_SET + "/" + (resource.FHIR).toLowerCase() } diff --git a/src/simplified/baseCard.tsx b/src/simplified/baseCard.tsx index 1ea5acc..5360568 100644 --- a/src/simplified/baseCard.tsx +++ b/src/simplified/baseCard.tsx @@ -2,7 +2,8 @@ import {Card} from "react-bootstrap"; import {useState, useEffect} from "react"; import { CSSTransition } from 'react-transition-group'; import State from "../state"; -import { ACTIVITY_DEFINITION, friendlyToFhir, PLAN_DEFINITION } from "./nameHelpers"; +import { Color } from "react-bootstrap/esm/types"; +import { ACTIVITY_DEFINITION, friendlyToFhir, PLAN_DEFINITION, QUESTIONNAIRE } from "./nameHelpers"; @@ -14,15 +15,19 @@ interface BaseCardProps { content?: JSX.Element, clickable?: boolean link?: string + bsBg?: string, + bsText?: Color | string, + bsBorder?: string, } export const BaseCard = (props: BaseCardProps) => { const [show, setShow] = useState(false); useEffect(() => { - setTimeout(() => { + const timeoutId = setTimeout(() => { setShow(true); }, props.wait); + return clearTimeout(timeoutId); }, [props.wait]); @@ -40,10 +45,12 @@ export const BaseCard = (props: BaseCardProps) => { classNames="res-card" > { - if (e.target.tagName !== "svg" && e.target.tagName !== "path" && props.clickable) { - setShow(false); - setTimeout(() => { + bg={props.bsBg} + text={props.bsText as Color} + border={props.bsBorder} + onClick={(e: any) => { + if (e.target.tagName !== "svg" && e.target.tagName !== "path" && props.clickable) { + setShow(false); if (State.get().bundle?.resources.length) { State.emit("save_changes_to_bundle_json"); State.get().bundle.set("pos", State.get().bundle.resources.length-1); @@ -53,7 +60,10 @@ export const BaseCard = (props: BaseCardProps) => { resourceType: "Bundle", entry: [ { - resource: {resourceType: resourceType} + resource: { + resourceType: resourceType, + meta: {profile: [props.profile]} + } }, { resource: { @@ -75,25 +85,19 @@ export const BaseCard = (props: BaseCardProps) => { } ] }; - if (isActivity) { - (json.entry[0].resource as any).meta = { - profile: [props.profile] - }; - } State.emit("load_json_resource", json); - }, 350) - } - }} + } + }} > - - {props.header} - - - {props.title} - - {content} - - + + {props.header} + + + {props.title} + + {content} + + ); diff --git a/src/simplified/collection.tsx b/src/simplified/collection.tsx index 19bd6cf..e28e746 100644 --- a/src/simplified/collection.tsx +++ b/src/simplified/collection.tsx @@ -20,10 +20,10 @@ const Collection = () => {  New Card -
@@ -35,13 +35,12 @@ const Collection = () => { const planTitleNode = SchemaUtils.getChildOfNode(resources[i+1], "title"); const firstExpression: string | undefined = SchemaUtils.getChildOfNodePath(resources[i+1], ["action", "condition", "expression", "expression"])?.value; const conditionExpressions: string[] = firstExpression ? [firstExpression] : []; - const profile = SchemaUtils.getChildOfNode(resource, "profile"); return
diff --git a/src/simplified/folder.tsx b/src/simplified/folder.tsx index 26d5a30..6cf4982 100644 --- a/src/simplified/folder.tsx +++ b/src/simplified/folder.tsx @@ -1,26 +1,29 @@ import {useState, useEffect} from "react"; -import { propTypes } from "react-bootstrap/esm/Image"; import {BaseCard} from"./baseCard"; import { CSSTransition } from 'react-transition-group'; import State from "../state"; -import { PLAN_DEFINITION } from "./nameHelpers"; +import { CloseButton } from "react-bootstrap"; +import { ACTIVITY_DEFINITION, getBorderPropsForType, PLAN_DEFINITION, profileToFriendlyResourceListEntry, profileToFriendlyResourceSelf } from "./nameHelpers"; interface FolderProps { actTitle: string, planTitle: string, conditionExpressions: string[], index: number, - type: string, + profile: string, link?: string wait: number } export const Folder = (props: FolderProps) => { const [show, setShow] = useState(false); + const friendlyName = profileToFriendlyResourceListEntry(props.profile)?.FRIENDLY ?? "Unknown"; + const resourceType = profileToFriendlyResourceSelf(props.profile)?.FHIR ?? ""; useEffect(() => { - setTimeout(() => { + const timeoutId = setTimeout(() => { setShow(true); }, props.wait); + return () => clearTimeout(timeoutId); }, [props.wait]); @@ -31,33 +34,40 @@ export const Folder = (props: FolderProps) => { classNames="res-folder" unmountOnExit > -
{ setShow(false); - setTimeout(() => { - State.emit("set_bundle_pos", props.index); - }, 300); + State.emit("set_bundle_pos", props.index); }}>
- +
+ {props.actTitle} {props.conditionExpressions.length > 0 ? `WHEN ${props.conditionExpressions[0]} IS TRUE` : ""} -
+ }/>
{State.get().bundle.resources.length > 2 && - } +
+ { + e.stopPropagation(); + State.emit("remove_from_bundle", props.index + 1); + State.emit("remove_from_bundle", props.index); + State.get().set("ui", {status:"collection"}) + }} + /> +
+ }
) diff --git a/src/simplified/nameHelpers.tsx b/src/simplified/nameHelpers.tsx index 56e3b96..3b209b1 100644 --- a/src/simplified/nameHelpers.tsx +++ b/src/simplified/nameHelpers.tsx @@ -1,5 +1,9 @@ import friendlyNames from "../../friendly-names.json"; +export type FriendlyResource = (typeof friendlyNames.RESOURCES) extends Array ? T : never; +export type FriendlyResourceSelf = FriendlyResource["SELF"]; +export type FriendlyResourceListEntry = FriendlyResource["LIST"] extends Array ? T : never; + const defaultUndefinedString = "undefined"; const getType = (type: string) => { @@ -20,36 +24,70 @@ export const DATA_ELEMENT = getType("DataElement"); export const VALUE_SET = getType("ValueSet"); export const STRUCTURE_DEFINITION = getType("StructureDefinition"); -export function getFhirSelf(resourceParent: any[], resourceType: string) { +export function getFhirSelf(resourceParent: FriendlyResource[], resourceType: string) { return resourceParent.find( (resource) => { - return resource.SELF.FHIR === resourceType|| resource.FHIR === resourceType; + return resource.SELF.FHIR === resourceType; } ); } -function elseIfUndefined(maybeUndefinedObject: any, ifDefinedFunction: any, replacementText: string): string { +function elseIfUndefined(maybeUndefinedObject: T, ifDefinedFunction: (input: Exclude) => string): string | undefined +function elseIfUndefined(maybeUndefinedObject: T, ifDefinedFunction: (input: Exclude) => string, replacementText: string): string +function elseIfUndefined(maybeUndefinedObject: T, ifDefinedFunction: (input: Exclude) => string, replacementText?: string): string | undefined { if (typeof(maybeUndefinedObject) === 'undefined') { - return replacementText + return replacementText ?? undefined; } else { - return ifDefinedFunction(maybeUndefinedObject); + return ifDefinedFunction(maybeUndefinedObject as Exclude); // This cast is necessary for some reason. todo: figure out how to remove it } } export const fhirToFriendly = (fhirWord: string) => { return elseIfUndefined(getFhirSelf(friendlyNames.RESOURCES, fhirWord) - ,((o:string) => (o)) + ,((o) => (o.SELF.FRIENDLY)) , defaultUndefinedString); } -export const friendlyToFhir = (friendlyWord: string) => { +export const friendlyToFhir = (friendlyWord: string) => { return elseIfUndefined(friendlyNames.RESOURCES.find(resource => resource.SELF.FRIENDLY == friendlyWord) - ,((object: { SELF: { FHIR: any; }; }) => object.SELF.FHIR) + ,((object) => object.SELF.FHIR) , defaultUndefinedString); } +export function profileToFriendlyResourceListEntry(profile?: string) { + if (!profile) { + return undefined; + } + for (const resource of friendlyNames.RESOURCES) { + const out = resource.LIST.find(res => res.PROFILE_URI == profile); + if (out) { + return out; + } + } +} + +export function profileToFriendlyResourceSelf(profile?: string) { + if (!profile) { + return undefined; + } + for (const resource of friendlyNames.RESOURCES) { + const found = resource.LIST.find(res => res.PROFILE_URI == profile); + if (found) { + return resource.SELF; + } + } +} + export const defaultProfileUriOfResourceType = (resourceType: string) => { return elseIfUndefined(getFhirSelf(friendlyNames.RESOURCES, resourceType) - ,((object: { SELF: { DEFAULT_PROFILE_URI: any; }; }) => object.SELF.DEFAULT_PROFILE_URI) - , defaultUndefinedString); + ,((object) => object.SELF.DEFAULT_PROFILE_URI)); } + +export function getBorderPropsForType(resourceType: string): string | undefined { + switch (resourceType) { + case ACTIVITY_DEFINITION: + return "activitydefinition"; + default: + return "questionnaire"; + } +} \ No newline at end of file diff --git a/src/simplified/selectView.tsx b/src/simplified/selectView.tsx index a37a0e1..27f4bce 100644 --- a/src/simplified/selectView.tsx +++ b/src/simplified/selectView.tsx @@ -3,48 +3,62 @@ import {BaseCard} from"./baseCard"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import {faCaretRight, faInfoCircle} from '@fortawesome/pro-solid-svg-icons'; import State from "../state"; +import { Container, Row, Col } from "react-bootstrap"; import friendlyNames from "../../friendly-names.json"; -import { makeLink, makeProfile } from "../helpers/schema-utils"; +import { ACTIVITY_DEFINITION, getBorderPropsForType } from "./nameHelpers"; const SelectView = () => { return (
-
-

Make a Card

- -
-
- { - friendlyNames.RESOURCES.map( - (resourceType) => ( - resourceType.LIST.map((resource, i) => ( - - } - wait={i * 25} - clickable={true} - profile={makeProfile(resource)} /> -
- ) - ) - ) - ) +
+

Make a Card

+ +
+
+ + + { + friendlyNames.RESOURCES.map( + (resourceType) => { + return resourceType.LIST.map( + (resource, i) => { + return ( +
+ + + + + FHIR Docs + + + } + wait={i*25} + clickable={true} + profile={resource.PROFILE_URI} + /> + +
); + } + ); } -
-
- ); + ) } - - export default SelectView \ No newline at end of file + + +
+ + ); +} + +export default SelectView diff --git a/src/simplified/simpleForm.tsx b/src/simplified/simpleForm.tsx index 22135f9..61b22d1 100644 --- a/src/simplified/simpleForm.tsx +++ b/src/simplified/simpleForm.tsx @@ -9,7 +9,7 @@ import * as SageUtils from "../helpers/sage-utils"; import hypertensionLibraryJson from "../../public/samples/hypertension-library.json"; import * as cql from "cql-execution"; -import { ACTIVITY_DEFINITION, PLAN_DEFINITION } from "./nameHelpers"; +import { ACTIVITY_DEFINITION, PLAN_DEFINITION, profileToFriendlyResourceListEntry } from "./nameHelpers"; const hypertensionLibrary: Library = hypertensionLibraryJson as Library; @@ -27,19 +27,18 @@ interface SimpleFormProps { planNode: SageNodeInitializedFreezerNode, } -const buildConditionFromSelection = (expression?: string): PlanDefinitionActionCondition | undefined => { +const buildConditionFromSelection = (expression?: string): PlanDefinitionActionCondition => { + var insertedExpression = ""; if (expression) { - return { - expression: { - language: 'text/cql', - expression: expression - }, - kind: 'applicability' - }; - } - else { - return undefined; + insertedExpression = expression; } + return { + expression: { + language: 'text/cql', + expression: insertedExpression + }, + kind: 'applicability' + }; } const getExpressionOptionsFromLibraries = (libraries: {library: cql.Library, url: string}[]) => { @@ -62,12 +61,12 @@ export const SimpleForm = (props:SimpleFormProps) => { // console.log(State.get().bundle?.resources?.[0]); const [title, setTitle] = useState(SchemaUtils.getChildOfNode(props.actNode, "title")?.value || ""); const [description, setDescription] = useState(SchemaUtils.getChildOfNode(props.actNode, "description")?.value || ""); - const [condition, setCondition] = useState(() => { + const [condition, setCondition] = useState(() => { return buildConditionFromSelection(SchemaUtils.getChildOfNodePath(props.planNode, ["action", "condition", "expression", "expression"])?.value) }); const [selectedLibrary, setSelectedLibrary] = useState(SchemaUtils.getChildOfNode(props.planNode, "library")?.value[0] || "") const [expressionOptions, setExpressionOptions] = useState({}); - + // Initialization useEffect( () => { @@ -91,112 +90,109 @@ export const SimpleForm = (props:SimpleFormProps) => { } }, [] - ) - - const [libraries, setLibraries] = useState<{library: cql.Library, url: string}[]>([]); - useEffect( - () => { - setExpressionOptions(getExpressionOptionsFromLibraries(libraries)); - return; - }, - [libraries], - ); - - // Import Library Modal - // Allows the user to import a new CQL Library to use as a condition - const [showImportModal, setShowImportModal] = useState(false); - const handleShowImportModal = () => setShowImportModal(true); - const handleCloseImportModal = () => setShowImportModal(false); - const [FhirLibrary, setFhirLibrary] = useState(); - const handleImportLibrary = () => { - if (FhirLibrary) { - try { - const parsedFhir = JSON.parse(FhirLibrary); - const newLib = SageUtils.getCqlExecutionLibraryFromInputLibraryResource(parsedFhir); - if (newLib) { - State.emit("load_library", newLib.library, newLib.url, parsedFhir); - } - } - catch (err) { - console.log("Could not parse FHIR Library as JSON object"); + ) + + const [libraries, setLibraries] = useState<{library: cql.Library, url: string}[]>([]); + useEffect( + () => { + setExpressionOptions(getExpressionOptionsFromLibraries(libraries)); return; - } - } - }; - const importModalElement = (<> - - - Import Library - - + }, + [libraries], + ); + + // Import Library Modal + // Allows the user to import a new CQL Library to use as a condition + const [showImportModal, setShowImportModal] = useState(false); + const handleShowImportModal = () => setShowImportModal(true); + const handleCloseImportModal = () => setShowImportModal(false); + const [FhirLibrary, setFhirLibrary] = useState(); + const handleImportLibrary = () => { + if (FhirLibrary) { + try { + const parsedFhir = JSON.parse(FhirLibrary); + const newLib = SageUtils.getCqlExecutionLibraryFromInputLibraryResource(parsedFhir); + if (newLib) { + State.emit("load_library", newLib.library, newLib.url, parsedFhir); + } + } + catch (err) { + console.log("Could not parse FHIR Library as JSON object"); + return; + } + } + }; + const importModalElement = (<> + + + Import Library + + Paste in the FHIR Library Resource that contains the condition you wish to use below:
- - setFhirLibrary(e.currentTarget.value)} - /> - + + setFhirLibrary(e.currentTarget.value)} + /> +
Please note that any dependencies of the pasted FHIR Library will not be automatically resolved or added to the final Bundle. -
- - - - -
- ); - - // All logic for saving the Simplified Form data into the underlying FHIR Resources should be performed here - const handleSaveResource = function() { - // console.log(props.actNode); - // console.log(title); - // console.log(description); - if (props.actNode.displayName == ACTIVITY_DEFINITION) { // Questionnaires have trouble saving otherwise - State.emit("value_change", SchemaUtils.getChildOfNode(props.actNode, "title"), title, false); - State.emit("value_change", SchemaUtils.getChildOfNode(props.actNode, "description"), description, false); - State.emit("value_change", SchemaUtils.getChildOfNode(props.actNode, "experimental"), State.get().experimental, false); - State.emit("value_change", SchemaUtils.getChildOfNode(props.actNode, "status"), State.get().status, false); - } - State.emit("value_change", SchemaUtils.getChildOfNode(props.planNode, "title"), title, false); - State.emit("value_change", SchemaUtils.getChildOfNode(props.planNode, "description"), description, false); - State.emit("value_change", SchemaUtils.getChildOfNode(props.planNode, "experimental"), State.get().experimental, false); - State.emit("value_change", SchemaUtils.getChildOfNode(props.planNode, "status"), State.get().status, false); - const titleNode = SchemaUtils.getChildOfNodePath(props.planNode, ["action", "title"]); - if (titleNode) { - State.emit("value_change", titleNode, title, false); - } - const descriptionNode = SchemaUtils.getChildOfNodePath(props.planNode, ["action", "description"]); - if (descriptionNode) { - State.emit("value_change", descriptionNode, description, false); - } - const conditionNode = SchemaUtils.getChildOfNodePath(props.planNode, ["action", "condition"]); - if (conditionNode) { - const conditionNodes = SchemaUtils.getChildrenFromObjectArrayNode(conditionNode); - State.emit("load_json_into", conditionNodes[0], condition); - State.emit("value_change", SchemaUtils.getChildOfNode(props.planNode, "library"), [selectedLibrary], false); - } - State.get().set("ui", {status:"collection"}) - } - - return ( -
- - {importModalElement} -
- - -

- {props.actNode.displayName} - /{PLAN_DEFINITION} -

- - + +

+ {props.actNode ? profileToFriendlyResourceListEntry(SchemaUtils.toFhir(props.actNode, false).meta?.profile?.[0])?.FRIENDLY ?? "Unknown Resource Type" : ""} +

+ + Title setTitle(e.currentTarget.value)} + type="text" + defaultValue={title} + onChange={(e) => setTitle(e.currentTarget.value)} /> - - - - + + + + Description setDescription(e.currentTarget.value)} + type="text" + defaultValue={description} + onChange={(e) => setDescription(e.currentTarget.value)} /> - - - - + + + + Condition - { - if (e.currentTarget.value == '') { - setCondition(buildConditionFromSelection()); - setSelectedLibrary(""); - } - else if (e.currentTarget.value == '[[import library]]') { - handleShowImportModal(); - setCondition(buildConditionFromSelection()); - setSelectedLibrary(""); - } - else { - setCondition(buildConditionFromSelection(e.currentTarget.value)); - setSelectedLibrary(expressionOptions[e.currentTarget.value].libraryUrl); - } - }} - value={condition?.expression?.expression || ""} - > - - {Object.keys(expressionOptions).map((v) => { - const exprOption = expressionOptions[v]; - return - })} - - + { + if (e.currentTarget.value == '') { + setCondition(buildConditionFromSelection()); + setSelectedLibrary(""); + } + else if (e.currentTarget.value == '[[import library]]') { + handleShowImportModal(); + setCondition(buildConditionFromSelection()); + setSelectedLibrary(""); + } + else { + setCondition(buildConditionFromSelection(e.currentTarget.value)); + setSelectedLibrary(expressionOptions[e.currentTarget.value].libraryUrl); + } + }} + value={condition.expression?.expression} + > + + {Object.keys(expressionOptions).map((v) => { + const exprOption = expressionOptions[v]; + return + })} + + - - - -
); - -} \ No newline at end of file + + + + ); + + } + \ No newline at end of file