diff --git a/.github/value-set-expand/upload.sh b/.github/value-set-expand/upload.sh
index 26eaa7e91..7b24f2ba6 100755
--- a/.github/value-set-expand/upload.sh
+++ b/.github/value-set-expand/upload.sh
@@ -3,14 +3,13 @@
FILENAME=$1
BASE="http://localhost:8080/fhir"
-echo "Upload $FILENAME"
-
RESOURCE_TYPE="$(jq -r .resourceType "$FILENAME")"
if [[ "$RESOURCE_TYPE" =~ ValueSet|CodeSystem ]]; then
URL="$(jq -r .url "$FILENAME")"
if [[ "$URL" =~ http://unitsofmeasure.org|http://snomed.info/sct|http://loinc.org|urn:ietf:bcp:13 ]]; then
echo "Skip creating the code system or value set $URL which is internal in Blaze"
else
+ echo "Upload $FILENAME"
curl -sf -H "Content-Type: application/fhir+json" -H "Prefer: return=minimal" -d @"$FILENAME" "$BASE/$RESOURCE_TYPE"
fi
fi
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f8dd5776d..2a32d46ed 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -2034,8 +2034,8 @@ jobs:
- name: Run Keycloak
run: docker compose -f modules/frontend-e2e/docker-compose.yml up -d keycloak
- - name: Sleep 20 Seconds
- run: sleep 20
+ - name: Install Dependencies
+ run: make -C modules/frontend-e2e install
- name: Run Everything Else
run: docker compose -f modules/frontend-e2e/docker-compose.yml up -d
@@ -2061,9 +2061,6 @@ jobs:
- name: Download Patient Resources
run: modules/frontend-e2e/download-patient-resources.sh
- - name: Install Playwright
- run: make -C modules/frontend-e2e install
-
- name: Run Playwright Tests
run: make -C modules/frontend-e2e test
diff --git a/modules/frontend-e2e/Makefile b/modules/frontend-e2e/Makefile
index 1bc2cc389..6479a1ca0 100644
--- a/modules/frontend-e2e/Makefile
+++ b/modules/frontend-e2e/Makefile
@@ -1,5 +1,5 @@
install:
- npm ci
+ npm install
npx playwright install --with-deps
test:
@@ -8,7 +8,10 @@ test:
test-dev:
DEV="1" npx playwright test
-test-ui-dev:
+test-dev-chromium:
+ DEV="1" npx playwright test --project chromium
+
+test-dev-ui:
DEV="1" npx playwright test --ui --project chromium
cloc-prod:
@@ -16,4 +19,4 @@ cloc-prod:
cloc-test:
cloc src
-.PHONY: fmt lint install test test-ui-dev test-coverage cloc-prod cloc-test clean
+.PHONY: fmt lint install test test-dev-chromium test-dev-ui test-coverage cloc-prod cloc-test clean
diff --git a/modules/frontend-e2e/docker-compose.yml b/modules/frontend-e2e/docker-compose.yml
index 21831f682..51ea9fccf 100644
--- a/modules/frontend-e2e/docker-compose.yml
+++ b/modules/frontend-e2e/docker-compose.yml
@@ -35,9 +35,12 @@ services:
backend:
image: "blaze:latest"
environment:
- JAVA_TOOL_OPTIONS: "-Xmx2g"
+ JAVA_TOOL_OPTIONS: "-Xmx4g"
LOG_LEVEL: "debug"
ENABLE_ADMIN_API: "true"
+ ENABLE_TERMINOLOGY_SERVICE: "true"
+ ENABLE_TERMINOLOGY_LOINC: "true"
+ DB_RESOURCE_CACHE_SIZE: "10000"
OPENID_PROVIDER_URL: "https://keycloak.localhost/realms/blaze"
OPENID_CLIENT_TRUST_STORE: "/app/keycloak-trust-store.p12"
OPENID_CLIENT_TRUST_STORE_PASS: "insecure"
diff --git a/modules/frontend-e2e/package-lock.json b/modules/frontend-e2e/package-lock.json
index a0034eea5..629b57452 100644
--- a/modules/frontend-e2e/package-lock.json
+++ b/modules/frontend-e2e/package-lock.json
@@ -8,6 +8,10 @@
"name": "frontend-e2e",
"version": "1.0.0",
"license": "ISC",
+ "dependencies": {
+ "de.medizininformatikinitiative.kerndatensatz.fall": "^2025.0.0",
+ "de.medizininformatikinitiative.kerndatensatz.laborbefund": "^2025.0.2"
+ },
"devDependencies": {
"@playwright/test": "^1.42.1",
"@types/node": "^22.0.0"
@@ -30,15 +34,25 @@
}
},
"node_modules/@types/node": {
- "version": "22.13.14",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz",
- "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==",
+ "version": "22.14.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
+ "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~6.20.0"
+ "undici-types": "~6.21.0"
}
},
+ "node_modules/de.medizininformatikinitiative.kerndatensatz.fall": {
+ "version": "2025.0.0",
+ "resolved": "https://packages.simplifier.net/de.medizininformatikinitiative.kerndatensatz.fall/2025.0.0",
+ "integrity": "sha1-pHVfeJFy4KiCQ5D7b5cEdR14eLk="
+ },
+ "node_modules/de.medizininformatikinitiative.kerndatensatz.laborbefund": {
+ "version": "2025.0.2",
+ "resolved": "https://packages.simplifier.net/de.medizininformatikinitiative.kerndatensatz.laborbefund/2025.0.2",
+ "integrity": "sha1-jVoi5AmZD1hbni15Pl9+2xmDnRg="
+ },
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@@ -87,9 +101,9 @@
}
},
"node_modules/undici-types": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
- "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
}
diff --git a/modules/frontend-e2e/package.json b/modules/frontend-e2e/package.json
index 93b4ab018..d789f2d74 100644
--- a/modules/frontend-e2e/package.json
+++ b/modules/frontend-e2e/package.json
@@ -10,5 +10,9 @@
"devDependencies": {
"@playwright/test": "^1.42.1",
"@types/node": "^22.0.0"
+ },
+ "dependencies": {
+ "de.medizininformatikinitiative.kerndatensatz.fall": "^2025.0.0",
+ "de.medizininformatikinitiative.kerndatensatz.laborbefund": "^2025.0.2"
}
}
diff --git a/modules/frontend-e2e/src/code-system.spec.ts b/modules/frontend-e2e/src/code-system.spec.ts
new file mode 100644
index 000000000..3c7f58806
--- /dev/null
+++ b/modules/frontend-e2e/src/code-system.spec.ts
@@ -0,0 +1,99 @@
+import { expect, type Locator, type Page, test } from '@playwright/test';
+
+function breadcrumbItem(page: Page, text: string): Locator {
+ return page.getByLabel('Breadcrumb').getByRole('listitem').filter({ hasText: text });
+}
+
+test.beforeEach('Sign In', async ({ page }) => {
+ await page.goto('/fhir/CodeSystem');
+
+ // Blaze Sign-In Page
+ await expect(page).toHaveTitle('Sign-In - Blaze');
+ await page.getByRole('button', { name: 'Sign in with Keycloak' }).click();
+
+ // Keycloak Sign-In Page
+ await expect(page).toHaveTitle('Sign in to Keycloak');
+ await page.getByLabel('Username or email').fill('john');
+ await page.getByLabel('Password', { exact: true }).fill('insecure');
+ await page.getByRole('button', { name: 'Sign In' }).click();
+
+ await expect(page).toHaveTitle('CodeSystem - Blaze');
+ await expect(breadcrumbItem(page, 'CodeSystem')).toBeVisible();
+});
+
+test('Search Page', async ({ page }) => {
+ await expect(page.getByTitle('CodeSystem History')).toBeVisible();
+ await expect(page.getByTitle('CodeSystem Metadata')).toBeVisible();
+ await expect(page.getByText('Total:')).toBeVisible();
+});
+
+test('Search for LOINC', async ({ page }) => {
+ await page.goto('/fhir/CodeSystem?url=http://loinc.org');
+
+ await expect(breadcrumbItem(page, 'CodeSystem')).toBeVisible();
+
+ await expect(page.getByRole('link', { name: 'LOINC Code System v2.78' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Url http://loinc.org' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Version 2.78' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Name LOINC' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Title LOINC Code System' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Status active' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Experimental false' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Publisher Regenstrief Institute, Inc.' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Content not-present' })).toBeVisible();
+
+ await page.getByRole('link', { name: 'LOINC Code System v2.78' }).click();
+
+ await expect(breadcrumbItem(page, 'CodeSystem')).toBeVisible();
+ await expect(breadcrumbItem(page, 'LOINC Code System v2.78')).toBeVisible();
+ await expect(page.getByRole('link', { name: 'LOINC Code System v2.78' })).toBeVisible();
+});
+
+test.describe('$validate-code', () => {
+ test.describe('LOINC 718-7', () => {
+ test('type-level', async ({ page }) => {
+ await page.getByRole('button', { name: 'Operations' }).click();
+ await page.getByRole('menuitem', { name: '$validate-code' }).click();
+
+ await expect(breadcrumbItem(page, 'CodeSystem')).toBeVisible();
+ await expect(breadcrumbItem(page, '$validate-code')).toBeVisible();
+
+ await page.getByRole('heading', { name: 'Parameters' }).click();
+ await page.getByLabel('URL').fill('http://loinc.org');
+ await page.getByLabel('Code').fill('718-7');
+ await page.getByRole('button', { name: 'Submit' }).click();
+
+ await expect(page.getByRole('listitem').filter({ hasText: 'Result true' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Display Hemoglobin [Mass/volume] in Blood' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Code 718-7' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'System http://loinc.org' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Version 2.78' })).toBeVisible();
+ });
+
+ test('instance-level', async ({ page }) => {
+ await page.goto('/fhir/CodeSystem?url=http://loinc.org');
+
+ await page.getByRole('link', { name: 'LOINC Code System v2.78' }).click();
+
+ await expect(breadcrumbItem(page, 'LOINC Code System v2.78')).toBeVisible();
+ await page.getByRole('button', { name: 'Operations' }).click();
+ await page.getByRole('menuitem', { name: '$validate-code' }).click();
+
+ await page.getByRole('heading', { name: 'LOINC Code System v2.78' }).click();
+
+ await expect(breadcrumbItem(page, 'CodeSystem')).toBeVisible();
+ await expect(breadcrumbItem(page, 'LOINC Code System v2.78')).toBeVisible();
+ await expect(breadcrumbItem(page, '$validate-code')).toBeVisible();
+
+ await page.getByRole('heading', { name: 'Parameters' }).click();
+ await page.getByLabel('Code').fill('718-7');
+ await page.getByRole('button', { name: 'Submit' }).click();
+
+ await expect(page.getByRole('listitem').filter({ hasText: 'Result true' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Display Hemoglobin [Mass/volume] in Blood' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Code 718-7' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'System http://loinc.org' })).toBeVisible();
+ await expect(page.getByRole('listitem').filter({ hasText: 'Version 2.78' })).toBeVisible();
+ });
+ });
+});
diff --git a/modules/frontend-e2e/src/fhir.spec.ts b/modules/frontend-e2e/src/fhir.spec.ts
index 4bc83666f..5413cc60f 100644
--- a/modules/frontend-e2e/src/fhir.spec.ts
+++ b/modules/frontend-e2e/src/fhir.spec.ts
@@ -43,7 +43,7 @@ test('History Page', async ({page}) => {
await page.getByRole('link', {name: 'History', exact: true}).click();
await expect(page).toHaveTitle("History - Blaze");
- await expect(page.getByText('Total: 92,300')).toBeVisible();
+ await expect(page.getByText('Total:')).toBeVisible();
});
test('Metadata Page', async ({page}) => {
@@ -71,7 +71,7 @@ test('Metadata Page', async ({page}) => {
await page.getByRole('link', {name: 'Resources'}).click();
await expect(page).toHaveTitle("Encounter - Blaze");
- await expect(page.getByText('Total: 4,769')).toBeVisible();
+ await expect(page.getByText('Total:')).toBeVisible();
});
test.describe('Admin', () => {
@@ -223,8 +223,8 @@ test.describe('Admin', () => {
await expect(page.getByText('Search Param URL ' + searchParamUrl)).toBeVisible();
// may appear later
- await expect(page.getByText('Total Resources 92.3 k')).toBeVisible({timeout: 30000});
- await expect(page.getByText('Resources Processed 92.3 k')).toBeVisible({timeout: 50000});
+ await expect(page.getByText('Total Resources')).toBeVisible({timeout: 30000});
+ await expect(page.getByText('Resources Processed')).toBeVisible({timeout: 50000});
await expect(page.getByText('Processing Duration')).toBeVisible({timeout: 50000});
await expect(page.getByText('Status completed')).toBeVisible({timeout: 50000});
});
@@ -250,7 +250,7 @@ test('Patients Page', async ({page}) => {
await expect(page).toHaveTitle("Patient - Blaze");
await expect(page.getByTitle('Patient History')).toBeVisible();
await expect(page.getByTitle('Patient Metadata')).toBeVisible();
- await expect(page.getByText('Total: 120')).toBeVisible();
+ await expect(page.getByText('Total:')).toBeVisible();
await page.getByTitle('Patient Metadata').click();
await expect(page).toHaveTitle("Patient - Metadata - Blaze");
@@ -270,7 +270,7 @@ test('Patients History Page', async ({page}) => {
await page.getByTitle('Patient History').click()
await expect(page).toHaveTitle("History - Patient - Blaze");
- await expect(page.getByText('Total: 120')).toBeVisible();
+ await expect(page.getByText('Total:')).toBeVisible();
});
test('Signing in after sign out goes to the Keycloak Sign-In Page', async ({page}) => {
diff --git a/modules/frontend-e2e/upload.sh b/modules/frontend-e2e/upload.sh
index c311d2db1..28c6f5f3f 100755
--- a/modules/frontend-e2e/upload.sh
+++ b/modules/frontend-e2e/upload.sh
@@ -12,9 +12,8 @@ blazectl --no-progress \
--token "$TOKEN" \
upload "$SCRIPT_DIR/../../.github/test-data/synthea"
-echo "Download KDS Fall Package..."
-wget -q --content-disposition "https://packages.simplifier.net/de.medizininformatikinitiative.kerndatensatz.fall/2025.0.0"
-tar xzf de.medizininformatikinitiative.kerndatensatz.fall-2025.0.0.tgz
-
echo "Upload KDS Fall Profile..."
-curl -sfH 'Content-Type: application/fhir+json' -H 'Prefer: return=minimal' --cacert "$CA_CERT" --oauth2-bearer "$TOKEN" -d @"package/StructureDefinition-mii-pr-fall-kontakt-gesundheitseinrichtung.json" "$BASE/StructureDefinition"
+curl -sfH 'Content-Type: application/fhir+json' -H 'Prefer: return=minimal' --cacert "$CA_CERT" --oauth2-bearer "$TOKEN" -d @"$SCRIPT_DIR/node_modules/de.medizininformatikinitiative.kerndatensatz.fall/StructureDefinition-mii-pr-fall-kontakt-gesundheitseinrichtung.json" "$BASE/StructureDefinition"
+
+echo "Upload one Value Set..."
+curl -sfH 'Content-Type: application/fhir+json' -H 'Prefer: return=minimal' --cacert "$CA_CERT" --oauth2-bearer "$TOKEN" -d @"$SCRIPT_DIR/node_modules/de.medizininformatikinitiative.kerndatensatz.laborbefund/ValueSet-mii-vs-labor-laborbereich.json" "$BASE/ValueSet"
diff --git a/modules/frontend/src/app.html b/modules/frontend/src/app.html
index 00c3f9b43..34fc1b573 100644
--- a/modules/frontend/src/app.html
+++ b/modules/frontend/src/app.html
@@ -2,7 +2,6 @@
-
diff --git a/modules/frontend/src/lib/breadcrumb.svelte b/modules/frontend/src/lib/breadcrumb.svelte
new file mode 100644
index 000000000..b09b78a55
--- /dev/null
+++ b/modules/frontend/src/lib/breadcrumb.svelte
@@ -0,0 +1,15 @@
+
+
+
diff --git a/modules/frontend/src/lib/breadcrumb/entry.svelte b/modules/frontend/src/lib/breadcrumb/entry.svelte
index c213d140e..421b5774f 100644
--- a/modules/frontend/src/lib/breadcrumb/entry.svelte
+++ b/modules/frontend/src/lib/breadcrumb/entry.svelte
@@ -1,6 +1,8 @@
-
-
- History
-
diff --git a/modules/frontend/src/lib/breadcrumb/resource-history.svelte b/modules/frontend/src/lib/breadcrumb/resource-history.svelte
new file mode 100644
index 000000000..cd6aee883
--- /dev/null
+++ b/modules/frontend/src/lib/breadcrumb/resource-history.svelte
@@ -0,0 +1,22 @@
+
+
+
+ {#if last}
+ History
+ {:else}
+ History
+ {/if}
+
diff --git a/modules/frontend/src/lib/breadcrumb/resource.svelte b/modules/frontend/src/lib/breadcrumb/resource.svelte
index 51e3e8980..2350f530e 100644
--- a/modules/frontend/src/lib/breadcrumb/resource.svelte
+++ b/modules/frontend/src/lib/breadcrumb/resource.svelte
@@ -1,12 +1,30 @@
- {page.params.id}
+ {#if last}
+ {name}
+ {:else}
+ {name}
+ {/if}
diff --git a/modules/frontend/src/lib/breadcrumb/type-history.svelte b/modules/frontend/src/lib/breadcrumb/type-history.svelte
new file mode 100644
index 000000000..640569a5e
--- /dev/null
+++ b/modules/frontend/src/lib/breadcrumb/type-history.svelte
@@ -0,0 +1,22 @@
+
+
+
+ {#if last}
+ History
+ {:else}
+ History
+ {/if}
+
diff --git a/modules/frontend/src/lib/breadcrumb/type.svelte b/modules/frontend/src/lib/breadcrumb/type.svelte
index bd6310f20..b06a6c36e 100644
--- a/modules/frontend/src/lib/breadcrumb/type.svelte
+++ b/modules/frontend/src/lib/breadcrumb/type.svelte
@@ -2,11 +2,21 @@
import { base } from '$app/paths';
import { page } from '$app/state';
import Entry from './entry.svelte';
+
+ interface Props {
+ type?: string;
+ last?: boolean;
+ }
+
+ let { type = page.params.type, last = false }: Props = $props();
- {page.params.type}
+ {#if last}
+ {type}
+ {:else}
+ {type}
+ {/if}
diff --git a/modules/frontend/src/lib/error-card.svelte b/modules/frontend/src/lib/error-card.svelte
index 240811108..f6a2a6020 100644
--- a/modules/frontend/src/lib/error-card.svelte
+++ b/modules/frontend/src/lib/error-card.svelte
@@ -1,4 +1,6 @@
{#if entry.fhirObject}
-
+
{#snippet header()}
{/snippet}
diff --git a/modules/frontend/src/lib/metadata.ts b/modules/frontend/src/lib/metadata.ts
index b640e9ea8..da011c62c 100644
--- a/modules/frontend/src/lib/metadata.ts
+++ b/modules/frontend/src/lib/metadata.ts
@@ -38,7 +38,7 @@ export async function fetchStructureDefinition(
fetch: typeof window.fetch = window.fetch
) {
const cached = structureDefinitionStore.get(type);
- if (cached) {
+ if (cached !== undefined) {
return cached;
}
diff --git a/modules/frontend/src/lib/resource.ts b/modules/frontend/src/lib/resource.ts
new file mode 100644
index 000000000..b634cc695
--- /dev/null
+++ b/modules/frontend/src/lib/resource.ts
@@ -0,0 +1,19 @@
+import type { CodeSystem, FhirResource, ValueSet } from 'fhir/r4';
+
+export function title(resource: FhirResource) {
+ if (resource.resourceType === 'CodeSystem') {
+ const codeSystem = resource as CodeSystem;
+ if (codeSystem.title && codeSystem.version) {
+ return `${codeSystem.title} v${codeSystem.version}`;
+ }
+ }
+
+ if (resource.resourceType === 'ValueSet') {
+ const valueSet = resource as ValueSet;
+ if (valueSet.title && valueSet.version) {
+ return `${valueSet.title} v${valueSet.version}`;
+ }
+ }
+
+ return `${resource.resourceType}/${resource.id}`;
+}
diff --git a/modules/frontend/src/lib/resource/json/array.svelte b/modules/frontend/src/lib/resource/json/array.svelte
index 0b9899ca9..587a1a7d0 100644
--- a/modules/frontend/src/lib/resource/json/array.svelte
+++ b/modules/frontend/src/lib/resource/json/array.svelte
@@ -8,10 +8,13 @@
}
let { indent, values }: Props = $props();
+
+ const maxLength = 100;
+ let length = Math.min(values.length, maxLength);
-{'[\n'}{#each values as value, index}{index < values.length - 1 ? ',\n' : '\n'}{/each}{' '.repeat(indent)}{']'}
+ />{index < length - 1 ? ',\n' : '\n'}{/each}{' '.repeat(indent)}]
diff --git a/modules/frontend/src/lib/resource/json/object.svelte b/modules/frontend/src/lib/resource/json/object.svelte
index 3d9aa19ae..9fd2a27fc 100644
--- a/modules/frontend/src/lib/resource/json/object.svelte
+++ b/modules/frontend/src/lib/resource/json/object.svelte
@@ -11,9 +11,8 @@
let { indent = 0, insideArray = false, object }: Props = $props();
-{insideArray ? ' '.repeat(indent) : ''}{'{\n'}{#each object.properties as property, index (property.name)}{/each}{' '.repeat(indent)}{'}'}
+ />{/each}{' '.repeat(indent)}{'}'}
diff --git a/modules/frontend/src/lib/resource/json/property.svelte b/modules/frontend/src/lib/resource/json/property.svelte
index b02f533dd..24d719d8d 100644
--- a/modules/frontend/src/lib/resource/json/property.svelte
+++ b/modules/frontend/src/lib/resource/json/property.svelte
@@ -16,11 +16,10 @@
let { indent, isLast, property }: Props = $props();
- let primitiveExtensions = $derived(
+ let primitiveExtensions =
!Array.isArray(property.value) && isPrimitive(property.value.type)
? (property.value as FhirPrimitive).extensions
- : undefined
- );
+ : undefined;
{' '.repeat(indent)}"{property.name}"
- import { isPrimitive } from '../resource-card.js';
+ import { type FhirObject, type FhirPrimitive, isPrimitive } from '../resource-card.js';
import PrimitiveValue from './primitive-value.svelte';
import Object from './object.svelte';
interface Props {
indent: number;
insideArray?: boolean;
- // eslint-disable-next-line
- value: any; //FhirObject | FhirPrimitive
+ value: FhirObject | FhirPrimitive;
}
let { indent, insideArray = false, value }: Props = $props();
+
+ function toPrimitive(value: FhirObject | FhirPrimitive): FhirPrimitive {
+ return value as FhirPrimitive;
+ }
+
+ function toObject(value: FhirObject | FhirPrimitive): FhirObject {
+ return value as FhirObject;
+ }
-
{#if isPrimitive(value.type)}{insideArray ? ' '.repeat(indent) : ''}{:else}{/if}
+ value={toPrimitive(value)}
+ />{:else}{/if}
diff --git a/modules/frontend/src/lib/resource/property.svelte b/modules/frontend/src/lib/resource/property.svelte
index 9842e0ca4..1066c72ee 100644
--- a/modules/frontend/src/lib/resource/property.svelte
+++ b/modules/frontend/src/lib/resource/property.svelte
@@ -5,6 +5,7 @@
type FhirPrimitive,
type FhirObject
} from './resource-card.js';
+
import type {
Attachment,
Identifier,
@@ -14,6 +15,7 @@
Reference,
Dosage
} from 'fhir/r4';
+
import PrimitiveValue from './primitive-value.svelte';
import ComplexValue from './complex-value.svelte';
import AttachmentValues from '$lib/values/attachment.svelte';
@@ -24,6 +26,8 @@
import ReferenceValues from '$lib/values/reference.svelte';
import DosageValues from '$lib/values/dosage.svelte';
+ import { toTitleCase } from '$lib/util.js';
+
interface Props {
property: FhirProperty;
}
@@ -34,8 +38,6 @@
return Array.isArray(x) ? x : [x];
}
- const name = property.name.substring(0, 1).toUpperCase() + property.name.substring(1);
-
const singlePrimitiveValue =
isPrimitive(property.type) && !Array.isArray(property.value)
? (property.value as FhirPrimitive)
@@ -79,8 +81,10 @@
}
-
-
{name}
+
+
+ {toTitleCase(property.humanName ?? property.name)}
+
{#if singlePrimitiveValue}
diff --git a/modules/frontend/src/lib/resource/resource-card.svelte b/modules/frontend/src/lib/resource/resource-card.svelte
index 755116c64..ef1842d67 100644
--- a/modules/frontend/src/lib/resource/resource-card.svelte
+++ b/modules/frontend/src/lib/resource/resource-card.svelte
@@ -1,5 +1,6 @@
-
+
{title}
{@render children?.()}
diff --git a/modules/frontend/src/lib/tailwind/description/left-aligned/row-5-4.svelte b/modules/frontend/src/lib/tailwind/description/left-aligned/row-5-4.svelte
index cc725d39f..e288e9510 100644
--- a/modules/frontend/src/lib/tailwind/description/left-aligned/row-5-4.svelte
+++ b/modules/frontend/src/lib/tailwind/description/left-aligned/row-5-4.svelte
@@ -1,13 +1,15 @@
-
+
{title}
{@render children?.()}
diff --git a/modules/frontend/src/lib/tailwind/dropdown.svelte b/modules/frontend/src/lib/tailwind/dropdown.svelte
new file mode 100644
index 000000000..916da2d5b
--- /dev/null
+++ b/modules/frontend/src/lib/tailwind/dropdown.svelte
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+ {#if open}
+
+
+ {@render children?.()}
+
+
+ {/if}
+
diff --git a/modules/frontend/src/lib/tailwind/dropdown/item.svelte b/modules/frontend/src/lib/tailwind/dropdown/item.svelte
new file mode 100644
index 000000000..eec999d97
--- /dev/null
+++ b/modules/frontend/src/lib/tailwind/dropdown/item.svelte
@@ -0,0 +1,17 @@
+
+
+
+
diff --git a/modules/frontend/src/lib/tailwind/form.svelte b/modules/frontend/src/lib/tailwind/form.svelte
new file mode 100644
index 000000000..88d968b61
--- /dev/null
+++ b/modules/frontend/src/lib/tailwind/form.svelte
@@ -0,0 +1,23 @@
+
+
+
diff --git a/modules/frontend/src/lib/tailwind/form/button-submit.svelte b/modules/frontend/src/lib/tailwind/form/button-submit.svelte
new file mode 100644
index 000000000..0d6358ff9
--- /dev/null
+++ b/modules/frontend/src/lib/tailwind/form/button-submit.svelte
@@ -0,0 +1,14 @@
+
+
+
diff --git a/modules/frontend/src/lib/tailwind/form/check-box.svelte b/modules/frontend/src/lib/tailwind/form/check-box.svelte
new file mode 100644
index 000000000..b217ab548
--- /dev/null
+++ b/modules/frontend/src/lib/tailwind/form/check-box.svelte
@@ -0,0 +1,46 @@
+
+
+
diff --git a/modules/frontend/src/lib/tailwind/form/check-boxes.svelte b/modules/frontend/src/lib/tailwind/form/check-boxes.svelte
new file mode 100644
index 000000000..ce793f65d
--- /dev/null
+++ b/modules/frontend/src/lib/tailwind/form/check-boxes.svelte
@@ -0,0 +1,22 @@
+
+
+
diff --git a/modules/frontend/src/lib/tailwind/form/section.svelte b/modules/frontend/src/lib/tailwind/form/section.svelte
new file mode 100644
index 000000000..1c6115abf
--- /dev/null
+++ b/modules/frontend/src/lib/tailwind/form/section.svelte
@@ -0,0 +1,24 @@
+
+
+
+
{name}
+ {#if description}
+
{description}
+ {/if}
+
+
+ {@render children?.()}
+
+
diff --git a/modules/frontend/src/lib/tailwind/form/text-field.svelte b/modules/frontend/src/lib/tailwind/form/text-field.svelte
new file mode 100644
index 000000000..445e2c3d2
--- /dev/null
+++ b/modules/frontend/src/lib/tailwind/form/text-field.svelte
@@ -0,0 +1,22 @@
+
+
+
diff --git a/modules/frontend/src/lib/tailwind/logo-card/card.svelte b/modules/frontend/src/lib/tailwind/logo-card/card.svelte
index a34a1e6d5..6199e4b62 100644
--- a/modules/frontend/src/lib/tailwind/logo-card/card.svelte
+++ b/modules/frontend/src/lib/tailwind/logo-card/card.svelte
@@ -1,9 +1,11 @@
+
+
+ $validate-code - CodeSystem - Blaze
+
+
+
+
+
+
+
+ $validate-code
+
+
+
+
+
+
+
+ {#if form?.incorrect}
+ {form.msg}
+ {/if}
+
+ {#if form?.result}
+
+ {/if}
+
diff --git a/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.server.ts b/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.server.ts
new file mode 100644
index 000000000..97eb6a37d
--- /dev/null
+++ b/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.server.ts
@@ -0,0 +1,56 @@
+import type { Actions } from './$types';
+import type { OperationOutcome, Parameters, ParametersParameter } from 'fhir/r4';
+import { base } from '$app/paths';
+import { fail } from '@sveltejs/kit';
+
+export const actions = {
+ default: async ({ request, fetch, params }) => {
+ const data = await request.formData();
+ const code = data.get('code') as string;
+ const display = data.get('display') as string;
+ const displayLanguage = data.get('displayLanguage') as string;
+
+ const parameters: ParametersParameter[] = [
+ {
+ name: 'code',
+ valueCode: code
+ }
+ ];
+
+ if (display !== '') {
+ parameters.push({
+ name: 'display',
+ valueString: display
+ });
+ }
+
+ if (displayLanguage !== '') {
+ parameters.push({
+ name: 'displayLanguage',
+ valueCode: displayLanguage
+ });
+ }
+
+ const res = await fetch(`${base}/CodeSystem/${params.id}/$validate-code`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/fhir+json', Accept: 'application/fhir+json' },
+ body: JSON.stringify({
+ resourceType: 'Parameters',
+ parameter: parameters
+ })
+ });
+
+ if (!res.ok) {
+ const error: OperationOutcome = await res.json();
+ return fail(400, {
+ code,
+ display,
+ displayLanguage,
+ incorrect: true,
+ msg: error.issue[0]?.diagnostics ?? error.issue[0]?.details?.text
+ });
+ }
+
+ return { code, display, displayLanguage, result: (await res.json()) as Parameters };
+ }
+} satisfies Actions;
diff --git a/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.svelte b/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.svelte
new file mode 100644
index 000000000..dbcda8923
--- /dev/null
+++ b/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.svelte
@@ -0,0 +1,61 @@
+
+
+
+ $validate-code - {title(data.codeSystem)} - Blaze
+
+
+
+
+
+
+
+
+ $validate-code
+
+
+
+
+
+
+ {title(data.codeSystem)}
+
+ {#if data.codeSystem.description}
+ {data.codeSystem.description}
+ {/if}
+
+
+
+ {#if form?.incorrect}
+ {form.msg}
+ {/if}
+
+ {#if form?.result}
+
+ {/if}
+
diff --git a/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.ts b/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.ts
new file mode 100644
index 000000000..1c7748a3d
--- /dev/null
+++ b/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.ts
@@ -0,0 +1,34 @@
+import type { PageLoad } from './$types';
+import type { Bundle, CodeSystem } from 'fhir/r4';
+
+import { base } from '$app/paths';
+import { error, type NumericRange } from '@sveltejs/kit';
+
+export const load: PageLoad = async ({ fetch, params }) => {
+ const res = await fetch(
+ `${base}/CodeSystem?_id=${params.id}&_elements=version,title,description`,
+ {
+ headers: {
+ Accept: 'application/fhir+json'
+ }
+ }
+ );
+
+ if (!res.ok) {
+ error(res.status as NumericRange<400, 599>, {
+ short: res.status == 404 ? 'Not Found' : res.status == 410 ? 'Gone' : undefined,
+ message:
+ res.status == 404
+ ? `The CodeSystem with ID ${params.id} was not found.`
+ : res.status == 410
+ ? `The CodeSystem with ID ${params.id} was deleted. Please look into the history.`
+ : `An error happened while loading the CodeSystem with ID ${params.id}. Please try again later.`
+ });
+ }
+
+ const bundle: Bundle = await res.json();
+
+ return {
+ codeSystem: bundle?.entry?.[0].resource as CodeSystem
+ };
+};
diff --git a/modules/frontend/src/routes/CodeSystem/[id=id]/operation-dropdown.svelte b/modules/frontend/src/routes/CodeSystem/[id=id]/operation-dropdown.svelte
new file mode 100644
index 000000000..6159c48a0
--- /dev/null
+++ b/modules/frontend/src/routes/CodeSystem/[id=id]/operation-dropdown.svelte
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/modules/frontend/src/routes/CodeSystem/operation-dropdown.svelte b/modules/frontend/src/routes/CodeSystem/operation-dropdown.svelte
new file mode 100644
index 000000000..a119d4fe6
--- /dev/null
+++ b/modules/frontend/src/routes/CodeSystem/operation-dropdown.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/modules/frontend/src/routes/CodeSystem/result-list.svelte b/modules/frontend/src/routes/CodeSystem/result-list.svelte
new file mode 100644
index 000000000..cdb893dc0
--- /dev/null
+++ b/modules/frontend/src/routes/CodeSystem/result-list.svelte
@@ -0,0 +1,51 @@
+
+
+
+
+ {result}
+
+ {#if !result}
+
+ {parameter(parameters, 'message')?.valueString}
+
+ {:else}
+ {@const display = parameter(parameters, 'display')?.valueString}
+ {#if display}
+
+ {display}
+
+ {/if}
+ {@const code = parameter(parameters, 'code')?.valueCode}
+ {#if code}
+
+ {code}
+
+ {/if}
+ {@const system = parameter(parameters, 'system')?.valueUri}
+ {#if system}
+
+ {system}
+
+ {/if}
+ {@const version = parameter(parameters, 'version')?.valueString}
+ {#if version}
+
+ {version}
+
+ {/if}
+ {/if}
+
diff --git a/modules/frontend/src/routes/ValueSet/$validate-code/+page.server.ts b/modules/frontend/src/routes/ValueSet/$validate-code/+page.server.ts
new file mode 100644
index 000000000..c33eb5226
--- /dev/null
+++ b/modules/frontend/src/routes/ValueSet/$validate-code/+page.server.ts
@@ -0,0 +1,108 @@
+import type { Actions } from './$types';
+import type { OperationOutcome, Parameters, ParametersParameter } from 'fhir/r4';
+import { base } from '$app/paths';
+import { fail } from '@sveltejs/kit';
+
+export const actions = {
+ default: async ({ request, fetch }) => {
+ const data = await request.formData();
+ const url = data.get('url') as string;
+ const valueSetVersion = data.get('valueSetVersion') as string;
+ const code = data.get('code') as string;
+ const system = data.get('system') as string;
+ const systemVersion = data.get('systemVersion') as string;
+ const display = data.get('display') as string;
+ const displayLanguage = data.get('displayLanguage') as string;
+ const inferSystem = Boolean(data.get('inferSystem'));
+
+ const parameters: ParametersParameter[] = [
+ {
+ name: 'url',
+ valueUri: url
+ },
+ {
+ name: 'code',
+ valueCode: code
+ }
+ ];
+
+ if (valueSetVersion !== '') {
+ parameters.push({
+ name: 'valueSetVersion',
+ valueString: valueSetVersion
+ });
+ }
+
+ if (system !== '') {
+ parameters.push({
+ name: 'system',
+ valueString: system
+ });
+ }
+
+ if (systemVersion !== '') {
+ parameters.push({
+ name: 'systemVersion',
+ valueString: systemVersion
+ });
+ }
+
+ if (display !== '') {
+ parameters.push({
+ name: 'display',
+ valueString: display
+ });
+ }
+
+ if (displayLanguage !== '') {
+ parameters.push({
+ name: 'displayLanguage',
+ valueCode: displayLanguage
+ });
+ }
+
+ if (inferSystem) {
+ parameters.push({
+ name: 'inferSystem',
+ valueBoolean: true
+ });
+ }
+
+ const res = await fetch(`${base}/ValueSet/$validate-code`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/fhir+json', Accept: 'application/fhir+json' },
+ body: JSON.stringify({
+ resourceType: 'Parameters',
+ parameter: parameters
+ })
+ });
+
+ if (!res.ok) {
+ const error: OperationOutcome = await res.json();
+ return fail(400, {
+ url,
+ valueSetVersion,
+ code,
+ system,
+ systemVersion,
+ display,
+ displayLanguage,
+ inferSystem,
+ incorrect: true,
+ msg: error.issue[0]?.diagnostics ?? error.issue[0]?.details?.text
+ });
+ }
+
+ return {
+ url,
+ valueSetVersion,
+ code,
+ system,
+ systemVersion,
+ display,
+ displayLanguage,
+ inferSystem,
+ result: (await res.json()) as Parameters
+ };
+ }
+} satisfies Actions;
diff --git a/modules/frontend/src/routes/ValueSet/$validate-code/+page.svelte b/modules/frontend/src/routes/ValueSet/$validate-code/+page.svelte
new file mode 100644
index 000000000..7edbf35ab
--- /dev/null
+++ b/modules/frontend/src/routes/ValueSet/$validate-code/+page.svelte
@@ -0,0 +1,59 @@
+
+
+
+ $validate-code - ValueSet - Blaze
+
+
+
+
+
+
+
+ $validate-code
+
+
+
+
+
+
+
+ {#if form?.incorrect}
+ {form.msg}
+ {/if}
+
+ {#if form?.result}
+
+ {/if}
+
diff --git a/modules/frontend/src/routes/ValueSet/[id=id]/$expand/+page.server.ts b/modules/frontend/src/routes/ValueSet/[id=id]/$expand/+page.server.ts
new file mode 100644
index 000000000..3f3d328d3
--- /dev/null
+++ b/modules/frontend/src/routes/ValueSet/[id=id]/$expand/+page.server.ts
@@ -0,0 +1,108 @@
+import type { Actions } from './$types';
+import type { OperationOutcome, ParametersParameter, ValueSet } from 'fhir/r4';
+import { base } from '$app/paths';
+import { fail } from '@sveltejs/kit';
+
+export const actions = {
+ default: async ({ request, fetch, params }) => {
+ const data = await request.formData();
+ const property = data.get('property') as string;
+ const displayLanguage = data.get('displayLanguage') as string;
+ const systemVersion = data.get('systemVersion') as string;
+ const includeDesignations = Boolean(data.get('includeDesignations'));
+ const includeDefinition = Boolean(data.get('includeDefinition'));
+ const activeOnly = Boolean(data.get('activeOnly'));
+ const excludeNested = Boolean(data.get('excludeNested'));
+
+ const parameters: ParametersParameter[] = [
+ {
+ name: 'count',
+ valueInteger: 100
+ }
+ ];
+
+ if (property !== '') {
+ parameters.push({
+ name: 'property',
+ valueString: property
+ });
+ }
+
+ if (displayLanguage !== '') {
+ parameters.push({
+ name: 'displayLanguage',
+ valueCode: displayLanguage
+ });
+ }
+
+ if (systemVersion !== '') {
+ parameters.push({
+ name: 'system-version',
+ valueString: systemVersion
+ });
+ }
+
+ if (includeDesignations) {
+ parameters.push({
+ name: 'includeDesignations',
+ valueBoolean: true
+ });
+ }
+
+ if (includeDefinition) {
+ parameters.push({
+ name: 'includeDefinition',
+ valueBoolean: true
+ });
+ }
+
+ if (activeOnly) {
+ parameters.push({
+ name: 'activeOnly',
+ valueBoolean: true
+ });
+ }
+
+ if (excludeNested) {
+ parameters.push({
+ name: 'excludeNested',
+ valueBoolean: true
+ });
+ }
+
+ const res = await fetch(`${base}/ValueSet/${params.id}/$expand`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/fhir+json', Accept: 'application/fhir+json' },
+ body: JSON.stringify({
+ resourceType: 'Parameters',
+ parameter: parameters
+ })
+ });
+
+ if (!res.ok) {
+ const error: OperationOutcome = await res.json();
+ return fail(400, {
+ property,
+ displayLanguage,
+ systemVersion,
+ includeDesignations,
+ includeDefinition,
+ activeOnly,
+ excludeNested,
+ incorrect: true,
+ msg: error.issue[0]?.diagnostics ?? error.issue[0]?.details?.text
+ });
+ }
+
+ return {
+ property,
+ displayLanguage,
+ systemVersion,
+ includeDesignations,
+ includeDefinition,
+ activeOnly,
+ excludeNested,
+ valueSet: (await res.json()) as ValueSet
+ };
+ }
+} satisfies Actions;
diff --git a/modules/frontend/src/routes/ValueSet/[id=id]/$expand/+page.svelte b/modules/frontend/src/routes/ValueSet/[id=id]/$expand/+page.svelte
new file mode 100644
index 000000000..4ecb676fb
--- /dev/null
+++ b/modules/frontend/src/routes/ValueSet/[id=id]/$expand/+page.svelte
@@ -0,0 +1,110 @@
+
+
+
+ $expand - {title(data.valueSet)} - Blaze
+
+
+
+
+
+
+
+
+ $expand
+
+
+
+
+
+
+ {title(data.valueSet)}
+
+ {#if data.valueSet.description}
+ {data.valueSet.description}
+ {/if}
+
+
+
+ {#if form?.incorrect}
+ {form.msg}
+ {/if}
+
+ {#if form?.valueSet?.expansion?.contains}
+
+ {#each form.valueSet.expansion.contains as contains}
+ -
+
+
+ {contains.display}
+
+
+
{contains.system}
+
+ {#if contains.version}
+
{contains.version}
+
+ {/if}
+
{contains.code}
+
+ {#if contains.designation}
+
+ {#each contains.designation as designation}
+ - {designation.value}
+ {/each}
+
+ {/if}
+
+
+ {/each}
+
+ {/if}
+
diff --git a/modules/frontend/src/routes/ValueSet/[id=id]/$expand/+page.ts b/modules/frontend/src/routes/ValueSet/[id=id]/$expand/+page.ts
new file mode 100644
index 000000000..a1d9b6660
--- /dev/null
+++ b/modules/frontend/src/routes/ValueSet/[id=id]/$expand/+page.ts
@@ -0,0 +1,31 @@
+import type { PageLoad } from './$types';
+import type { Bundle, ValueSet } from 'fhir/r4';
+
+import { base } from '$app/paths';
+import { error, type NumericRange } from '@sveltejs/kit';
+
+export const load: PageLoad = async ({ fetch, params }) => {
+ const res = await fetch(`${base}/ValueSet?_id=${params.id}&_elements=version,title,description`, {
+ headers: {
+ Accept: 'application/fhir+json'
+ }
+ });
+
+ if (!res.ok) {
+ error(res.status as NumericRange<400, 599>, {
+ short: res.status == 404 ? 'Not Found' : res.status == 410 ? 'Gone' : undefined,
+ message:
+ res.status == 404
+ ? `The ValueSet with ID ${params.id} was not found.`
+ : res.status == 410
+ ? `The ValueSet with ID ${params.id} was deleted. Please look into the history.`
+ : `An error happened while loading the ValueSet with ID ${params.id}. Please try again later.`
+ });
+ }
+
+ const bundle: Bundle = await res.json();
+
+ return {
+ valueSet: bundle?.entry?.[0].resource as ValueSet
+ };
+};
diff --git a/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.server.ts b/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.server.ts
new file mode 100644
index 000000000..a432dac48
--- /dev/null
+++ b/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.server.ts
@@ -0,0 +1,91 @@
+import type { Actions } from './$types';
+import type { OperationOutcome, Parameters, ParametersParameter } from 'fhir/r4';
+import { base } from '$app/paths';
+import { fail } from '@sveltejs/kit';
+
+export const actions = {
+ default: async ({ request, fetch, params }) => {
+ const data = await request.formData();
+ const code = data.get('code') as string;
+ const system = data.get('system') as string;
+ const systemVersion = data.get('systemVersion') as string;
+ const display = data.get('display') as string;
+ const displayLanguage = data.get('displayLanguage') as string;
+ const inferSystem = Boolean(data.get('inferSystem'));
+
+ const parameters: ParametersParameter[] = [
+ {
+ name: 'code',
+ valueCode: code
+ }
+ ];
+
+ if (system !== '') {
+ parameters.push({
+ name: 'system',
+ valueString: system
+ });
+ }
+
+ if (systemVersion !== '') {
+ parameters.push({
+ name: 'systemVersion',
+ valueString: systemVersion
+ });
+ }
+
+ if (display !== '') {
+ parameters.push({
+ name: 'display',
+ valueString: display
+ });
+ }
+
+ if (displayLanguage !== '') {
+ parameters.push({
+ name: 'displayLanguage',
+ valueCode: displayLanguage
+ });
+ }
+
+ if (inferSystem) {
+ parameters.push({
+ name: 'inferSystem',
+ valueBoolean: true
+ });
+ }
+
+ const res = await fetch(`${base}/ValueSet/${params.id}/$validate-code`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/fhir+json', Accept: 'application/fhir+json' },
+ body: JSON.stringify({
+ resourceType: 'Parameters',
+ parameter: parameters
+ })
+ });
+
+ if (!res.ok) {
+ const error: OperationOutcome = await res.json();
+ return fail(400, {
+ code,
+ system,
+ systemVersion,
+ display,
+ displayLanguage,
+ inferSystem,
+ incorrect: true,
+ msg: error.issue[0]?.diagnostics ?? error.issue[0]?.details?.text
+ });
+ }
+
+ return {
+ code,
+ system,
+ systemVersion,
+ display,
+ displayLanguage,
+ inferSystem,
+ result: (await res.json()) as Parameters
+ };
+ }
+} satisfies Actions;
diff --git a/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.svelte b/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.svelte
new file mode 100644
index 000000000..eee4ddf05
--- /dev/null
+++ b/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.svelte
@@ -0,0 +1,68 @@
+
+
+
+ $validate-code - {title(data.valueSet)} - Blaze
+
+
+
+
+
+
+
+
+ $validate-code
+
+
+
+
+
+
+ {title(data.valueSet)}
+
+ {#if data.valueSet.description}
+ {data.valueSet.description}
+ {/if}
+
+
+
+ {#if form?.incorrect}
+ {form.msg}
+ {/if}
+
+ {#if form?.result}
+
+ {/if}
+
diff --git a/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.ts b/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.ts
new file mode 100644
index 000000000..a1d9b6660
--- /dev/null
+++ b/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.ts
@@ -0,0 +1,31 @@
+import type { PageLoad } from './$types';
+import type { Bundle, ValueSet } from 'fhir/r4';
+
+import { base } from '$app/paths';
+import { error, type NumericRange } from '@sveltejs/kit';
+
+export const load: PageLoad = async ({ fetch, params }) => {
+ const res = await fetch(`${base}/ValueSet?_id=${params.id}&_elements=version,title,description`, {
+ headers: {
+ Accept: 'application/fhir+json'
+ }
+ });
+
+ if (!res.ok) {
+ error(res.status as NumericRange<400, 599>, {
+ short: res.status == 404 ? 'Not Found' : res.status == 410 ? 'Gone' : undefined,
+ message:
+ res.status == 404
+ ? `The ValueSet with ID ${params.id} was not found.`
+ : res.status == 410
+ ? `The ValueSet with ID ${params.id} was deleted. Please look into the history.`
+ : `An error happened while loading the ValueSet with ID ${params.id}. Please try again later.`
+ });
+ }
+
+ const bundle: Bundle = await res.json();
+
+ return {
+ valueSet: bundle?.entry?.[0].resource as ValueSet
+ };
+};
diff --git a/modules/frontend/src/routes/ValueSet/[id=id]/operation-dropdown.svelte b/modules/frontend/src/routes/ValueSet/[id=id]/operation-dropdown.svelte
new file mode 100644
index 000000000..8570cecb4
--- /dev/null
+++ b/modules/frontend/src/routes/ValueSet/[id=id]/operation-dropdown.svelte
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/modules/frontend/src/routes/ValueSet/operation-dropdown.svelte b/modules/frontend/src/routes/ValueSet/operation-dropdown.svelte
new file mode 100644
index 000000000..7e7b4f14e
--- /dev/null
+++ b/modules/frontend/src/routes/ValueSet/operation-dropdown.svelte
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/modules/frontend/src/routes/ValueSet/result-list.svelte b/modules/frontend/src/routes/ValueSet/result-list.svelte
new file mode 100644
index 000000000..cdb893dc0
--- /dev/null
+++ b/modules/frontend/src/routes/ValueSet/result-list.svelte
@@ -0,0 +1,51 @@
+
+
+
+
+ {result}
+
+ {#if !result}
+
+ {parameter(parameters, 'message')?.valueString}
+
+ {:else}
+ {@const display = parameter(parameters, 'display')?.valueString}
+ {#if display}
+
+ {display}
+
+ {/if}
+ {@const code = parameter(parameters, 'code')?.valueCode}
+ {#if code}
+
+ {code}
+
+ {/if}
+ {@const system = parameter(parameters, 'system')?.valueUri}
+ {#if system}
+
+ {system}
+
+ {/if}
+ {@const version = parameter(parameters, 'version')?.valueString}
+ {#if version}
+
+ {version}
+
+ {/if}
+ {/if}
+
diff --git a/modules/frontend/src/routes/[type=type]/+error.svelte b/modules/frontend/src/routes/[type=type]/+error.svelte
index 4d9c2cded..a26e25b16 100644
--- a/modules/frontend/src/routes/[type=type]/+error.svelte
+++ b/modules/frontend/src/routes/[type=type]/+error.svelte
@@ -1,6 +1,7 @@
@@ -14,13 +15,11 @@
-
+
+
+
+
+
diff --git a/modules/frontend/src/routes/[type=type]/[id=id]/+page.svelte b/modules/frontend/src/routes/[type=type]/[id=id]/+page.svelte
index f7edda9e4..4fa44eba9 100644
--- a/modules/frontend/src/routes/[type=type]/[id=id]/+page.svelte
+++ b/modules/frontend/src/routes/[type=type]/[id=id]/+page.svelte
@@ -1,7 +1,7 @@
- {page.params.type}/{page.params.id} - Blaze
+ {title(resource)} - Blaze
{#if data.resource}
{#snippet header()}
-
+
{/snippet}
diff --git a/modules/frontend/src/routes/[type=type]/[id=id]/_history/+page.svelte b/modules/frontend/src/routes/[type=type]/[id=id]/_history/+page.svelte
index 8c95546a3..ef2b32ea4 100644
--- a/modules/frontend/src/routes/[type=type]/[id=id]/_history/+page.svelte
+++ b/modules/frontend/src/routes/[type=type]/[id=id]/_history/+page.svelte
@@ -3,10 +3,11 @@
import { page } from '$app/state';
+ import Breadcrumb from '$lib/breadcrumb.svelte';
import BreadcrumbEntryHome from '$lib/breadcrumb/home.svelte';
import BreadcrumbEntryType from '$lib/breadcrumb/type.svelte';
import BreadcrumbEntryResource from '$lib/breadcrumb/resource.svelte';
- import BreadcrumbEntryHistory from '$lib/breadcrumb/history.svelte';
+ import BreadcrumbEntryHistory from '$lib/breadcrumb/resource-history.svelte';
import EntryCard from '$lib/history/entry-card.svelte';
@@ -18,14 +19,12 @@
-
+
+
+
+
+
+
diff --git a/modules/frontend/src/routes/[type=type]/[id=id]/_history/+page.ts b/modules/frontend/src/routes/[type=type]/[id=id]/_history/+page.ts
index 2e9fb6ac9..bd08b8211 100644
--- a/modules/frontend/src/routes/[type=type]/[id=id]/_history/+page.ts
+++ b/modules/frontend/src/routes/[type=type]/[id=id]/_history/+page.ts
@@ -5,7 +5,7 @@ import { error, type NumericRange } from '@sveltejs/kit';
import { transformBundle } from '$lib/resource/resource-card.js';
export const load: PageLoad = async ({ fetch, params }) => {
- const res = await fetch(`${base}/${params.type}/${params.id}/_history`, {
+ const res = await fetch(`${base}/${params.type}/${params.id}/_history?_count&_summary=true`, {
headers: { Accept: 'application/fhir+json' }
});
diff --git a/modules/frontend/src/routes/[type=type]/[id=id]/_history/[vid=vid]/+page.svelte b/modules/frontend/src/routes/[type=type]/[id=id]/_history/[vid=vid]/+page.svelte
index 6b37cbd83..8e7496e69 100644
--- a/modules/frontend/src/routes/[type=type]/[id=id]/_history/[vid=vid]/+page.svelte
+++ b/modules/frontend/src/routes/[type=type]/[id=id]/_history/[vid=vid]/+page.svelte
@@ -3,6 +3,7 @@
import { page } from '$app/state';
+ import Breadcrumb from '$lib/breadcrumb.svelte';
import BreadcrumbEntryHome from '$lib/breadcrumb/home.svelte';
import BreadcrumbEntryType from '$lib/breadcrumb/type.svelte';
import BreadcrumbEntryResource from '$lib/breadcrumb/resource.svelte';
@@ -17,14 +18,12 @@
-
+
+
+
+
+
+
diff --git a/modules/frontend/src/routes/[type=type]/[id=id]/history-button.svelte b/modules/frontend/src/routes/[type=type]/[id=id]/history-button.svelte
new file mode 100644
index 000000000..49f418650
--- /dev/null
+++ b/modules/frontend/src/routes/[type=type]/[id=id]/history-button.svelte
@@ -0,0 +1,10 @@
+
+
+History
diff --git a/modules/frontend/src/routes/[type=type]/__history-page/[pageId=pageId]/+page.svelte b/modules/frontend/src/routes/[type=type]/__history-page/[pageId=pageId]/+page.svelte
index ab75b86ef..7eff85169 100644
--- a/modules/frontend/src/routes/[type=type]/__history-page/[pageId=pageId]/+page.svelte
+++ b/modules/frontend/src/routes/[type=type]/__history-page/[pageId=pageId]/+page.svelte
@@ -3,9 +3,10 @@
import { page } from '$app/state';
+ import Breadcrumb from '$lib/breadcrumb.svelte';
import BreadcrumbEntryHome from '$lib/breadcrumb/home.svelte';
import BreadcrumbEntryType from '$lib/breadcrumb/type.svelte';
- import BreadcrumbEntryHistory from '$lib/breadcrumb/history.svelte';
+ import BreadcrumbEntryHistory from '$lib/breadcrumb/type-history.svelte';
import BreadcrumbEntryPage from '$lib/breadcrumb/page.svelte';
import TotalCard from '$lib/total-card.svelte';
@@ -20,19 +21,17 @@
-
+
+
+
+
+
+
-
+
{#if data.bundle.total !== undefined}
{/if}
diff --git a/modules/frontend/src/routes/[type=type]/__page/[pageId=pageId]/+page.svelte b/modules/frontend/src/routes/[type=type]/__page/[pageId=pageId]/+page.svelte
index 679d66a02..a6d67113c 100644
--- a/modules/frontend/src/routes/[type=type]/__page/[pageId=pageId]/+page.svelte
+++ b/modules/frontend/src/routes/[type=type]/__page/[pageId=pageId]/+page.svelte
@@ -5,6 +5,7 @@
import { page } from '$app/state';
import { fade, slide } from 'svelte/transition';
+ import Breadcrumb from '$lib/breadcrumb.svelte';
import BreadcrumbEntryHome from '$lib/breadcrumb/home.svelte';
import BreadcrumbEntryType from '$lib/breadcrumb/type.svelte';
import BreadcrumbEntryPage from '$lib/breadcrumb/page.svelte';
@@ -34,13 +35,11 @@
diff --git a/modules/frontend/src/routes/[type=type]/_history/+page.svelte b/modules/frontend/src/routes/[type=type]/_history/+page.svelte
index 7cabd51ff..1c6c8b9ff 100644
--- a/modules/frontend/src/routes/[type=type]/_history/+page.svelte
+++ b/modules/frontend/src/routes/[type=type]/_history/+page.svelte
@@ -3,9 +3,10 @@
import { page } from '$app/state';
+ import Breadcrumb from '$lib/breadcrumb.svelte';
import BreadcrumbEntryHome from '$lib/breadcrumb/home.svelte';
import BreadcrumbEntryType from '$lib/breadcrumb/type.svelte';
- import BreadcrumbEntryHistory from '$lib/breadcrumb/history.svelte';
+ import BreadcrumbEntryHistory from '$lib/breadcrumb/type-history.svelte';
import TotalCard from '$lib/total-card.svelte';
import TotalBadge from '$lib/total-badge.svelte';
@@ -19,18 +20,16 @@
-
+
{#if data.bundle.total !== undefined}
{/if}
diff --git a/modules/frontend/src/routes/[type=type]/history-button.svelte b/modules/frontend/src/routes/[type=type]/history-button.svelte
index fab35aebf..20ad78e61 100644
--- a/modules/frontend/src/routes/[type=type]/history-button.svelte
+++ b/modules/frontend/src/routes/[type=type]/history-button.svelte
@@ -5,6 +5,6 @@
History
diff --git a/modules/frontend/src/routes/[type=type]/metadata-button.svelte b/modules/frontend/src/routes/[type=type]/metadata-button.svelte
index b5edabaa8..2634441a2 100644
--- a/modules/frontend/src/routes/[type=type]/metadata-button.svelte
+++ b/modules/frontend/src/routes/[type=type]/metadata-button.svelte
@@ -5,6 +5,6 @@
Metadata
diff --git a/modules/frontend/src/routes/__admin/jobs/new/re-index/+page.server.ts b/modules/frontend/src/routes/__admin/jobs/new/re-index/+page.server.ts
index 68e7226ed..181bc34da 100644
--- a/modules/frontend/src/routes/__admin/jobs/new/re-index/+page.server.ts
+++ b/modules/frontend/src/routes/__admin/jobs/new/re-index/+page.server.ts
@@ -1,6 +1,6 @@
import type { Actions } from './$types';
-import { base } from '$app/paths';
import type { OperationOutcome, Task } from 'fhir/r4';
+import { base } from '$app/paths';
import { fail, redirect } from '@sveltejs/kit';
export const actions = {
diff --git a/modules/frontend/src/routes/__admin/jobs/re-index/[id=id]/+page.svelte b/modules/frontend/src/routes/__admin/jobs/re-index/[id=id]/+page.svelte
index 14dbc2dae..3b28ef3ff 100644
--- a/modules/frontend/src/routes/__admin/jobs/re-index/[id=id]/+page.svelte
+++ b/modules/frontend/src/routes/__admin/jobs/re-index/[id=id]/+page.svelte
@@ -2,10 +2,10 @@
import type { PageProps } from './$types';
import { invalidateAll } from '$app/navigation';
- import Status from '$lib/jobs/re-index/status.svelte';
import DescriptionList from '$lib/tailwind/description/left-aligned/list.svelte';
- import DateTime from '$lib/values/date-time.svelte';
import Row from '$lib/tailwind/description/left-aligned/row-3-2.svelte';
+ import Status from '$lib/jobs/re-index/status.svelte';
+ import DateTime from '$lib/values/date-time.svelte';
import prettyNum from '$lib/pretty-num';
import humanizeDuration from 'humanize-duration';
diff --git a/modules/frontend/src/routes/__history-page/[pageId=pageId]/+page.svelte b/modules/frontend/src/routes/__history-page/[pageId=pageId]/+page.svelte
index 78aa4ef03..bafcf2e63 100644
--- a/modules/frontend/src/routes/__history-page/[pageId=pageId]/+page.svelte
+++ b/modules/frontend/src/routes/__history-page/[pageId=pageId]/+page.svelte
@@ -14,7 +14,7 @@
-
+
{#if data.bundle.total !== undefined}
{/if}
diff --git a/modules/frontend/src/routes/_history/+page.svelte b/modules/frontend/src/routes/_history/+page.svelte
index 78aa4ef03..bafcf2e63 100644
--- a/modules/frontend/src/routes/_history/+page.svelte
+++ b/modules/frontend/src/routes/_history/+page.svelte
@@ -14,7 +14,7 @@
-
+
{#if data.bundle.total !== undefined}
{/if}
diff --git a/modules/frontend/src/routes/metadata/[type=type]/+page.svelte b/modules/frontend/src/routes/metadata/[type=type]/+page.svelte
index 2be4542e1..3d971b123 100644
--- a/modules/frontend/src/routes/metadata/[type=type]/+page.svelte
+++ b/modules/frontend/src/routes/metadata/[type=type]/+page.svelte
@@ -3,13 +3,15 @@
import { base } from '$app/paths';
import { page } from '$app/state';
+ import { sortByProperty2 } from '$lib/util';
+ import Breadcrumb from '$lib/breadcrumb.svelte';
import BreadcrumbEntryHome from '$lib/breadcrumb/home.svelte';
import BreadcrumbEntryMetadata from '$lib/breadcrumb/metadata.svelte';
import BreadcrumbEntryType from '$lib/breadcrumb/type.svelte';
import DescriptionList from '$lib/tailwind/description/left-aligned/list.svelte';
import Row from '$lib/tailwind/description/left-aligned/row-5-4.svelte';
- import { sortByProperty2 } from '$lib/util';
+ import ExternalLink from '$lib/values/util/external-link.svelte';
let { data }: PageProps = $props();
@@ -26,13 +28,11 @@
@@ -44,6 +44,11 @@
{#snippet description()}
Resources
{/snippet}
+
+ {page.params.type}
+
{#if resource?.supportedProfile !== undefined}