feat(frontend): unify page shell and move create flows to dedicated routes
This commit is contained in:
parent
e20c18c2ee
commit
0253b2377d
50 changed files with 2235 additions and 1042 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { createLabResult, deleteLabResult, getLabResults, updateLabResult } from '$lib/api';
|
||||
import { deleteLabResult, getLabResults, updateLabResult } from '$lib/api';
|
||||
import { fail } from '@sveltejs/kit';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
|
|
@ -41,38 +41,6 @@ export const load: PageServerLoad = async ({ url }) => {
|
|||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
create: async ({ request }) => {
|
||||
const form = await request.formData();
|
||||
const collected_at = form.get('collected_at') as string;
|
||||
const test_code = form.get('test_code') as string;
|
||||
const test_name_original = form.get('test_name_original') as string;
|
||||
const value_num = form.get('value_num') as string;
|
||||
const unit_original = form.get('unit_original') as string;
|
||||
const flag = form.get('flag') as string;
|
||||
const lab = form.get('lab') as string;
|
||||
|
||||
if (!collected_at || !test_code) {
|
||||
return fail(400, { error: 'Date and test code are required' });
|
||||
}
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
collected_at,
|
||||
test_code
|
||||
};
|
||||
if (test_name_original) body.test_name_original = test_name_original;
|
||||
if (value_num) body.value_num = Number(value_num);
|
||||
if (unit_original) body.unit_original = unit_original;
|
||||
if (flag) body.flag = flag;
|
||||
if (lab) body.lab = lab;
|
||||
|
||||
try {
|
||||
await createLabResult(body);
|
||||
return { created: true };
|
||||
} catch (e) {
|
||||
return fail(500, { error: (e as Error).message });
|
||||
}
|
||||
},
|
||||
|
||||
update: async ({ request }) => {
|
||||
const form = await request.formData();
|
||||
const id = form.get('id') as string;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import type { ActionData, PageData } from './$types';
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import FlashMessages from '$lib/components/FlashMessages.svelte';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { baseSelectClass, baseTextareaClass } from '$lib/components/forms/form-classes';
|
||||
import FormSectionCard from '$lib/components/forms/FormSectionCard.svelte';
|
||||
import SimpleSelect from '$lib/components/forms/SimpleSelect.svelte';
|
||||
import { Pencil, X } from 'lucide-svelte';
|
||||
import { preventIfNotConfirmed } from '$lib/utils/forms';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -30,8 +32,6 @@
|
|||
H: 'health-flag-pill health-flag-pill--high'
|
||||
};
|
||||
|
||||
let showForm = $state(false);
|
||||
let selectedFlag = $state('');
|
||||
let editingId = $state<string | null>(null);
|
||||
let editCollectedAt = $state('');
|
||||
let editTestCode = $state('');
|
||||
|
|
@ -185,54 +185,49 @@
|
|||
return '—';
|
||||
}
|
||||
|
||||
const flagOptions = flags.map((f) => ({ value: f, label: f }));
|
||||
const textareaClass = `${baseTextareaClass} min-h-[5rem] resize-y`;
|
||||
let filterFlagOverride = $state<string | null>(null);
|
||||
let filterLatestOnlyOverride = $state<'true' | 'false' | null>(null);
|
||||
const activeFilterFlag = $derived(filterFlagOverride ?? (data.flag ?? ''));
|
||||
const activeLatestOnly = $derived(filterLatestOnlyOverride ?? (data.latestOnly ? 'true' : 'false'));
|
||||
const flashMessages = $derived([
|
||||
...(form?.error ? [{ kind: 'error' as const, text: form.error }] : []),
|
||||
...(form?.deleted ? [{ kind: 'success' as const, text: m["labResults_deleted"]() }] : []),
|
||||
...(form?.updated ? [{ kind: 'success' as const, text: m["labResults_updated"]() }] : [])
|
||||
]);
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{m["labResults_title"]()} — innercontext</title></svelte:head>
|
||||
|
||||
<div class="editorial-page space-y-4">
|
||||
<section class="editorial-hero reveal-1">
|
||||
<p class="editorial-kicker">{m["nav_appSubtitle"]()}</p>
|
||||
<h2 class="editorial-title">{m["labResults_title"]()}</h2>
|
||||
<p class="editorial-subtitle">{m["labResults_count"]({ count: data.resultPage.total })}</p>
|
||||
<div class="lab-results-meta-strip">
|
||||
<span class="lab-results-meta-pill">
|
||||
{m['labResults_view']()}: {data.latestOnly ? m['labResults_viewLatest']() : m['labResults_viewAll']()}
|
||||
{#if data.flag}
|
||||
· {m['labResults_flagFilter']()} {data.flag}
|
||||
<PageHeader
|
||||
title={m["labResults_title"]()}
|
||||
kicker={m["nav_appSubtitle"]()}
|
||||
subtitle={m["labResults_count"]({ count: data.resultPage.total })}
|
||||
>
|
||||
{#snippet meta()}
|
||||
<div class="lab-results-meta-strip">
|
||||
<span class="lab-results-meta-pill">
|
||||
{m['labResults_view']()}: {data.latestOnly ? m['labResults_viewLatest']() : m['labResults_viewAll']()}
|
||||
{#if data.flag}
|
||||
· {m['labResults_flagFilter']()} {data.flag}
|
||||
{/if}
|
||||
</span>
|
||||
<span class="lab-results-meta-pill">
|
||||
{m['labResults_flag']()}: {flaggedCount}
|
||||
</span>
|
||||
{#if data.test_code}
|
||||
<span class="lab-results-meta-pill lab-results-meta-pill--alert">{data.test_code}</span>
|
||||
{/if}
|
||||
</span>
|
||||
<span class="lab-results-meta-pill">
|
||||
{m['labResults_flag']()}: {flaggedCount}
|
||||
</span>
|
||||
{#if data.test_code}
|
||||
<span class="lab-results-meta-pill lab-results-meta-pill--alert">{data.test_code}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="editorial-toolbar">
|
||||
<Button variant="outline" onclick={() => (showForm = !showForm)}>
|
||||
{showForm ? m.common_cancel() : m["labResults_addNew"]()}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#if form?.error}
|
||||
<div class="editorial-alert editorial-alert--error">{form.error}</div>
|
||||
{/if}
|
||||
{#if form?.created}
|
||||
<div class="editorial-alert editorial-alert--success">{m["labResults_added"]()}</div>
|
||||
{/if}
|
||||
{#if form?.deleted}
|
||||
<div class="editorial-alert editorial-alert--success">{m["labResults_deleted"]()}</div>
|
||||
{/if}
|
||||
{#if form?.updated}
|
||||
<div class="editorial-alert editorial-alert--success">{m["labResults_updated"]()}</div>
|
||||
{/if}
|
||||
{#snippet actions()}
|
||||
<Button variant="outline" href="/health/lab-results/new">{m["labResults_addNew"]()}</Button>
|
||||
{/snippet}
|
||||
</PageHeader>
|
||||
|
||||
<FlashMessages messages={flashMessages} />
|
||||
|
||||
{#if editingId}
|
||||
<FormSectionCard title={m["labResults_editTitle"]()} className="reveal-2">
|
||||
|
|
@ -457,56 +452,6 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showForm}
|
||||
<FormSectionCard title={m["labResults_newTitle"]()} className="reveal-2">
|
||||
<form method="POST" action="?/create" use:enhance class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="space-y-1">
|
||||
<Label for="collected_at">{m["labResults_date"]()}</Label>
|
||||
<Input id="collected_at" name="collected_at" type="date" required />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="test_code"
|
||||
>{m["labResults_loincCode"]()} <span class="text-xs text-muted-foreground"
|
||||
>({m["labResults_loincExample"]()})</span
|
||||
></Label
|
||||
>
|
||||
<Input id="test_code" name="test_code" required placeholder="718-7" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="test_name_original">{m["labResults_testName"]()}</Label>
|
||||
<Input
|
||||
id="test_name_original"
|
||||
name="test_name_original"
|
||||
placeholder={m["labResults_testNamePlaceholder"]()}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="lab_create">{m["labResults_lab"]()}</Label>
|
||||
<Input id="lab_create" name="lab" placeholder={m["labResults_labPlaceholder"]()} />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="value_num">{m["labResults_value"]()}</Label>
|
||||
<Input id="value_num" name="value_num" type="number" step="any" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="unit_original">{m["labResults_unit"]()}</Label>
|
||||
<Input id="unit_original" name="unit_original" placeholder={m["labResults_unitPlaceholder"]()} />
|
||||
</div>
|
||||
<SimpleSelect
|
||||
id="flag_create"
|
||||
name="flag"
|
||||
label={m["labResults_flag"]()}
|
||||
options={flagOptions}
|
||||
placeholder={m["labResults_flagNone"]()}
|
||||
bind:value={selectedFlag}
|
||||
/>
|
||||
<div class="flex items-end">
|
||||
<Button type="submit">{m.common_add()}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormSectionCard>
|
||||
{/if}
|
||||
|
||||
{#if data.totalPages > 1}
|
||||
<div class="editorial-panel lab-results-pager reveal-2 flex items-center justify-between gap-3">
|
||||
<Button
|
||||
|
|
@ -545,9 +490,7 @@
|
|||
method="POST"
|
||||
action="?/delete"
|
||||
use:enhance
|
||||
onsubmit={(event) => {
|
||||
if (!confirm(m['labResults_confirmDelete']())) event.preventDefault();
|
||||
}}
|
||||
onsubmit={(event) => preventIfNotConfirmed(event, m['labResults_confirmDelete']())}
|
||||
>
|
||||
<input type="hidden" name="id" value={r.record_id} />
|
||||
<Button
|
||||
|
|
@ -573,9 +516,7 @@
|
|||
method="POST"
|
||||
action="?/delete"
|
||||
use:enhance
|
||||
onsubmit={(event) => {
|
||||
if (!confirm(m['labResults_confirmDelete']())) event.preventDefault();
|
||||
}}
|
||||
onsubmit={(event) => preventIfNotConfirmed(event, m['labResults_confirmDelete']())}
|
||||
>
|
||||
<input type="hidden" name="id" value={r.record_id} />
|
||||
<Button type="submit" variant="ghost" size="sm" class="text-destructive hover:text-destructive">
|
||||
|
|
@ -615,7 +556,7 @@
|
|||
{/snippet}
|
||||
|
||||
{#snippet mobileCard(r: LabResultItem)}
|
||||
<div class="products-mobile-card lab-results-mobile-card flex flex-col gap-1">
|
||||
<div class="editorial-mobile-card lab-results-mobile-card flex flex-col gap-1">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="flex min-w-0 flex-col gap-1">
|
||||
<button
|
||||
|
|
@ -649,7 +590,7 @@
|
|||
{/snippet}
|
||||
|
||||
<!-- Desktop: table -->
|
||||
<div class="products-table-shell lab-results-table hidden md:block reveal-2">
|
||||
<div class="editorial-table-shell lab-results-table hidden md:block reveal-2">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
|
|
@ -667,9 +608,9 @@
|
|||
{#if group.label}
|
||||
<TableRow>
|
||||
<TableCell colspan={7} class="bg-muted/35 py-2">
|
||||
<div class="products-section-title text-xs uppercase tracking-[0.12em]">
|
||||
{group.label}
|
||||
</div>
|
||||
<div class="editorial-section-title text-xs uppercase tracking-[0.12em]">
|
||||
{group.label}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/if}
|
||||
|
|
@ -692,7 +633,7 @@
|
|||
<div class="lab-results-mobile-grid flex flex-col gap-3 md:hidden reveal-3">
|
||||
{#each displayGroups as group (group.key)}
|
||||
{#if group.label}
|
||||
<div class="products-section-title text-xs uppercase tracking-[0.12em]">{group.label}</div>
|
||||
<div class="editorial-section-title text-xs uppercase tracking-[0.12em]">{group.label}</div>
|
||||
{/if}
|
||||
{#each group.items as r (r.record_id)}
|
||||
{@render mobileCard(r)}
|
||||
|
|
|
|||
42
frontend/src/routes/health/lab-results/new/+page.server.ts
Normal file
42
frontend/src/routes/health/lab-results/new/+page.server.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { createLabResult } from '$lib/api';
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async ({ request }) => {
|
||||
const form = await request.formData();
|
||||
const collected_at = form.get('collected_at') as string;
|
||||
const test_code = form.get('test_code') as string;
|
||||
const test_name_original = form.get('test_name_original') as string;
|
||||
const value_num = form.get('value_num') as string;
|
||||
const unit_original = form.get('unit_original') as string;
|
||||
const flag = form.get('flag') as string;
|
||||
const lab = form.get('lab') as string;
|
||||
|
||||
if (!collected_at || !test_code) {
|
||||
return fail(400, { error: 'Date and test code are required' });
|
||||
}
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
collected_at,
|
||||
test_code
|
||||
};
|
||||
if (test_name_original) body.test_name_original = test_name_original;
|
||||
if (value_num) body.value_num = Number(value_num);
|
||||
if (unit_original) body.unit_original = unit_original;
|
||||
if (flag) body.flag = flag;
|
||||
if (lab) body.lab = lab;
|
||||
|
||||
try {
|
||||
await createLabResult(body);
|
||||
} catch (error) {
|
||||
return fail(500, { error: (error as Error).message });
|
||||
}
|
||||
|
||||
redirect(303, '/health/lab-results');
|
||||
}
|
||||
};
|
||||
68
frontend/src/routes/health/lab-results/new/+page.svelte
Normal file
68
frontend/src/routes/health/lab-results/new/+page.svelte
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import FlashMessages from '$lib/components/FlashMessages.svelte';
|
||||
import FormSectionCard from '$lib/components/forms/FormSectionCard.svelte';
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import SimpleSelect from '$lib/components/forms/SimpleSelect.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
import type { ActionData } from './$types';
|
||||
|
||||
let { form }: { form: ActionData } = $props();
|
||||
let selectedFlag = $state('');
|
||||
|
||||
const flags = ['N', 'ABN', 'POS', 'NEG', 'L', 'H'];
|
||||
const flagOptions = flags.map((flag) => ({ value: flag, label: flag }));
|
||||
const flashMessages = $derived(form?.error ? [{ kind: 'error' as const, text: form.error }] : []);
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{m['labResults_newTitle']()} — innercontext</title></svelte:head>
|
||||
|
||||
<div class="editorial-page space-y-4">
|
||||
<PageHeader
|
||||
title={m['labResults_newTitle']()}
|
||||
kicker={m['nav_appSubtitle']()}
|
||||
backHref={resolve('/health/lab-results')}
|
||||
backLabel={m['labResults_title']()}
|
||||
subtitle={m['labResults_newSubtitle']()}
|
||||
/>
|
||||
|
||||
<FlashMessages messages={flashMessages} />
|
||||
|
||||
<FormSectionCard title={m['labResults_newTitle']()} className="reveal-2">
|
||||
<form method="POST" class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<p class="sm:col-span-2 text-sm leading-6 text-muted-foreground">{m['labResults_newSectionIntro']()}</p>
|
||||
<div class="space-y-1">
|
||||
<Label for="collected_at">{m['labResults_date']()}</Label>
|
||||
<Input id="collected_at" name="collected_at" type="date" required />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="test_code">{m['labResults_loincCode']()} <span class="text-xs text-muted-foreground">({m['labResults_loincExample']()})</span></Label>
|
||||
<Input id="test_code" name="test_code" required placeholder="718-7" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="test_name_original">{m['labResults_testName']()}</Label>
|
||||
<Input id="test_name_original" name="test_name_original" placeholder={m['labResults_testNamePlaceholder']()} />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="lab">{m['labResults_lab']()}</Label>
|
||||
<Input id="lab" name="lab" placeholder={m['labResults_labPlaceholder']()} />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="value_num">{m['labResults_value']()}</Label>
|
||||
<Input id="value_num" name="value_num" type="number" step="any" />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="unit_original">{m['labResults_unit']()}</Label>
|
||||
<Input id="unit_original" name="unit_original" placeholder={m['labResults_unitPlaceholder']()} />
|
||||
</div>
|
||||
<SimpleSelect id="flag" name="flag" label={m['labResults_flag']()} options={flagOptions} placeholder={m['labResults_flagNone']()} bind:value={selectedFlag} />
|
||||
<div class="flex items-center justify-end gap-2 sm:col-span-2">
|
||||
<Button type="button" variant="outline" href={resolve('/health/lab-results')}>{m.common_cancel()}</Button>
|
||||
<Button type="submit">{m.common_add()}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormSectionCard>
|
||||
</div>
|
||||
|
|
@ -1,35 +1,8 @@
|
|||
import { createMedication, getMedications } from '$lib/api';
|
||||
import { fail } from '@sveltejs/kit';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
import { getMedications } from '$lib/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const kind = url.searchParams.get('kind') ?? undefined;
|
||||
const medications = await getMedications({ kind });
|
||||
return { medications, kind };
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
create: async ({ request }) => {
|
||||
const form = await request.formData();
|
||||
const kind = form.get('kind') as string;
|
||||
const product_name = form.get('product_name') as string;
|
||||
const active_substance = form.get('active_substance') as string;
|
||||
const notes = form.get('notes') as string;
|
||||
|
||||
if (!kind || !product_name) {
|
||||
return fail(400, { error: 'Kind and product name are required' });
|
||||
}
|
||||
|
||||
try {
|
||||
await createMedication({
|
||||
kind,
|
||||
product_name,
|
||||
active_substance: active_substance || undefined,
|
||||
notes: notes || undefined
|
||||
});
|
||||
return { created: true };
|
||||
} catch (e) {
|
||||
return fail(500, { error: (e as Error).message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,19 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import type { ActionData, PageData } from './$types';
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
import type { PageData } from './$types';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import FormSectionCard from '$lib/components/forms/FormSectionCard.svelte';
|
||||
import SimpleSelect from '$lib/components/forms/SimpleSelect.svelte';
|
||||
|
||||
let { data, form }: { data: PageData; form: ActionData } = $props();
|
||||
|
||||
const kinds = ['prescription', 'otc', 'supplement', 'herbal', 'other'];
|
||||
let showForm = $state(false);
|
||||
let kind = $state('supplement');
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
const kindPills: Record<string, string> = {
|
||||
prescription: 'health-kind-pill health-kind-pill--prescription',
|
||||
|
|
@ -31,7 +22,6 @@
|
|||
other: m["medications_kindOther"]
|
||||
};
|
||||
|
||||
const kindOptions = $derived(kinds.map((k) => ({ value: k, label: kindLabels[k]?.() ?? k })));
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{m.medications_title()} — innercontext</title></svelte:head>
|
||||
|
|
@ -39,53 +29,13 @@
|
|||
<div class="editorial-page space-y-4">
|
||||
<section class="editorial-hero reveal-1">
|
||||
<p class="editorial-kicker">{m["nav_appSubtitle"]()}</p>
|
||||
<h2 class="editorial-title">{m.medications_title()}</h2>
|
||||
<h1 class="editorial-title">{m.medications_title()}</h1>
|
||||
<p class="editorial-subtitle">{m.medications_count({ count: data.medications.length })}</p>
|
||||
<div class="editorial-toolbar">
|
||||
<Button variant="outline" onclick={() => (showForm = !showForm)}>
|
||||
{showForm ? m.common_cancel() : m["medications_addNew"]()}
|
||||
</Button>
|
||||
<Button variant="outline" href="/health/medications/new">{m["medications_addNew"]()}</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{#if form?.error}
|
||||
<div class="editorial-alert editorial-alert--error">{form.error}</div>
|
||||
{/if}
|
||||
{#if form?.created}
|
||||
<div class="editorial-alert editorial-alert--success">{m.medications_added()}</div>
|
||||
{/if}
|
||||
|
||||
{#if showForm}
|
||||
<FormSectionCard title={m["medications_newTitle"]()} className="reveal-2">
|
||||
<form method="POST" action="?/create" use:enhance class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="col-span-2">
|
||||
<SimpleSelect
|
||||
id="kind"
|
||||
name="kind"
|
||||
label={m.medications_kind()}
|
||||
options={kindOptions}
|
||||
bind:value={kind}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="product_name">{m["medications_productName"]()}</Label>
|
||||
<Input id="product_name" name="product_name" required placeholder={m["medications_productNamePlaceholder"]()} />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="active_substance">{m["medications_activeSubstance"]()}</Label>
|
||||
<Input id="active_substance" name="active_substance" placeholder={m["medications_activeSubstancePlaceholder"]()} />
|
||||
</div>
|
||||
<div class="space-y-1 col-span-2">
|
||||
<Label for="notes">{m.medications_notes()}</Label>
|
||||
<Input id="notes" name="notes" />
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<Button type="submit">{m.common_add()}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormSectionCard>
|
||||
{/if}
|
||||
|
||||
<div class="editorial-panel reveal-2 space-y-3">
|
||||
{#each data.medications as med (med.record_id)}
|
||||
<div class="health-entry-row">
|
||||
|
|
|
|||
34
frontend/src/routes/health/medications/new/+page.server.ts
Normal file
34
frontend/src/routes/health/medications/new/+page.server.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { createMedication } from '$lib/api';
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async ({ request }) => {
|
||||
const form = await request.formData();
|
||||
const kind = form.get('kind') as string;
|
||||
const product_name = form.get('product_name') as string;
|
||||
const active_substance = form.get('active_substance') as string;
|
||||
const notes = form.get('notes') as string;
|
||||
|
||||
if (!kind || !product_name) {
|
||||
return fail(400, { error: 'Kind and product name are required' });
|
||||
}
|
||||
|
||||
try {
|
||||
await createMedication({
|
||||
kind,
|
||||
product_name,
|
||||
active_substance: active_substance || undefined,
|
||||
notes: notes || undefined
|
||||
});
|
||||
} catch (error) {
|
||||
return fail(500, { error: (error as Error).message });
|
||||
}
|
||||
|
||||
redirect(303, '/health/medications');
|
||||
}
|
||||
};
|
||||
65
frontend/src/routes/health/medications/new/+page.svelte
Normal file
65
frontend/src/routes/health/medications/new/+page.svelte
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
import FlashMessages from '$lib/components/FlashMessages.svelte';
|
||||
import FormSectionCard from '$lib/components/forms/FormSectionCard.svelte';
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import SimpleSelect from '$lib/components/forms/SimpleSelect.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
import type { ActionData } from './$types';
|
||||
|
||||
let { form }: { form: ActionData } = $props();
|
||||
let kind = $state('supplement');
|
||||
|
||||
const kinds = ['prescription', 'otc', 'supplement', 'herbal', 'other'];
|
||||
const kindLabels: Record<string, () => string> = {
|
||||
prescription: m['medications_kindPrescription'],
|
||||
otc: m['medications_kindOtc'],
|
||||
supplement: m['medications_kindSupplement'],
|
||||
herbal: m['medications_kindHerbal'],
|
||||
other: m['medications_kindOther']
|
||||
};
|
||||
const kindOptions = kinds.map((entry) => ({ value: entry, label: kindLabels[entry]?.() ?? entry }));
|
||||
const flashMessages = $derived(form?.error ? [{ kind: 'error' as const, text: form.error }] : []);
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{m['medications_newTitle']()} — innercontext</title></svelte:head>
|
||||
|
||||
<div class="editorial-page space-y-4">
|
||||
<PageHeader
|
||||
title={m['medications_newTitle']()}
|
||||
kicker={m['nav_appSubtitle']()}
|
||||
backHref={resolve('/health/medications')}
|
||||
backLabel={m.medications_title()}
|
||||
subtitle={m['medications_newSubtitle']()}
|
||||
/>
|
||||
|
||||
<FlashMessages messages={flashMessages} />
|
||||
|
||||
<FormSectionCard title={m['medications_newTitle']()} className="reveal-2">
|
||||
<form method="POST" class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<p class="sm:col-span-2 text-sm leading-6 text-muted-foreground">{m['medications_newSectionIntro']()}</p>
|
||||
<div class="sm:col-span-2">
|
||||
<SimpleSelect id="kind" name="kind" label={m.medications_kind()} options={kindOptions} bind:value={kind} />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="product_name">{m['medications_productName']()}</Label>
|
||||
<Input id="product_name" name="product_name" required placeholder={m['medications_productNamePlaceholder']()} />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="active_substance">{m['medications_activeSubstance']()}</Label>
|
||||
<Input id="active_substance" name="active_substance" placeholder={m['medications_activeSubstancePlaceholder']()} />
|
||||
</div>
|
||||
<div class="space-y-1 sm:col-span-2">
|
||||
<Label for="notes">{m.medications_notes()}</Label>
|
||||
<Input id="notes" name="notes" />
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-2 sm:col-span-2">
|
||||
<Button type="button" variant="outline" href={resolve('/health/medications')}>{m.common_cancel()}</Button>
|
||||
<Button type="submit">{m.common_add()}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormSectionCard>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue