From dee579ab46ecf590a4ff9503e3c3433f910430d6 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 11 Feb 2024 02:45:38 +0100 Subject: [PATCH 1/4] fix: filter bar height feat: add filter button to patients table --- src/app.html | 2 +- src/lib/components/filter/Autocomplete.svelte | 2 +- src/lib/components/filter/FilterBar.svelte | 18 ++++++++++--- src/lib/components/table/PatientTable.svelte | 27 ++++++++++++++----- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/app.html b/src/app.html index 84ffad1..5f0dd88 100644 --- a/src/app.html +++ b/src/app.html @@ -6,7 +6,7 @@ %sveltekit.head% - +
%sveltekit.body%
diff --git a/src/lib/components/filter/Autocomplete.svelte b/src/lib/components/filter/Autocomplete.svelte index 64d535f..9f1f822 100644 --- a/src/lib/components/filter/Autocomplete.svelte +++ b/src/lib/components/filter/Autocomplete.svelte @@ -23,7 +23,7 @@ export let placeholder: string | undefined = undefined; export let padding = true; export let cls = ""; - export let inputCls = "w-full bg-transparent"; + export let inputCls = "w-full bg-transparent outline-none"; export let asTextInput = false; export let idInputName: string | undefined = undefined; diff --git a/src/lib/components/filter/FilterBar.svelte b/src/lib/components/filter/FilterBar.svelte index 1b481d6..6047c78 100644 --- a/src/lib/components/filter/FilterBar.svelte +++ b/src/lib/components/filter/FilterBar.svelte @@ -183,10 +183,8 @@ } -
-
+
+
{#each activeFilters as fdata, i} { activeFilters = []; + searchVal = ""; updateFilter(); }} > @@ -239,3 +238,14 @@ {/if}
+ + diff --git a/src/lib/components/table/PatientTable.svelte b/src/lib/components/table/PatientTable.svelte index 24c4f00..3c047ae 100644 --- a/src/lib/components/table/PatientTable.svelte +++ b/src/lib/components/table/PatientTable.svelte @@ -1,12 +1,13 @@ @@ -85,22 +57,16 @@ - + -{#if loadError} - -{:else} - - - -{/if} + diff --git a/src/lib/components/table/FilteredPatientTable.svelte b/src/lib/components/table/FilteredPatientTable.svelte index 98b3705..bef0116 100644 --- a/src/lib/components/table/FilteredPatientTable.svelte +++ b/src/lib/components/table/FilteredPatientTable.svelte @@ -4,11 +4,9 @@ import FilterBar from "$lib/components/filter/FilterBar.svelte"; import type { PaginationRequest, SortRequest } from "$lib/shared/model"; import type { FilterQdata } from "$lib/components/filter/types"; - import { trpc, type RouterOutput } from "$lib/shared/trpc"; + import { type RouterOutput } from "$lib/shared/trpc"; import { PATIENT_FILTER } from "$lib/components/filter/filters"; import { getQueryUrl } from "$lib/shared/util"; - import LoadingBar from "$lib/components/ui/LoadingBar.svelte"; - import ErrorMessage from "$lib/components/ui/ErrorMessage.svelte"; import type { ZPatientsQuery } from "$lib/shared/model/validation"; import { z } from "zod"; import { browser } from "$app/environment"; @@ -18,9 +16,6 @@ export let patients: RouterOutput["patient"]["list"]; export let baseUrl: string; - let loadingBar: LoadingBar | undefined; - let loadError: Error | null = null; - function paginationUpdate(pagination: PaginationRequest) { updateQuery({ filter: query.filter, @@ -46,22 +41,6 @@ // Update page URL const url = getQueryUrl(q, baseUrl); goto(url, { replaceState: true, keepFocus: true }); - - loadingBar?.start(); - trpc() - .patient.list.query(q) - .then((p) => { - patients = p; - loadError = null; - loadingBar?.reset(); - }) - .catch((err) => { - loadError = err; - loadingBar?.error(); - }) - .finally(() => { - query = q; - }); } } @@ -75,16 +54,10 @@ - + -{#if loadError} - -{:else} - - - -{/if} + From 39b97c60428b35344de5a1d472f91d99e603fb7f Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Tue, 13 Feb 2024 00:34:10 +0100 Subject: [PATCH 3/4] feat: add hide/delete buttons --- src/lib/components/filter/FilterBar.svelte | 9 ++-- src/lib/components/filter/FilterChip.svelte | 8 +++- src/lib/components/filter/filters.ts | 7 +++ src/lib/components/form/PatientForm.svelte | 18 +++++--- src/lib/components/table/PatientField.svelte | 2 +- src/lib/components/table/PatientTable.svelte | 4 +- src/lib/server/query/patient.ts | 10 ++++- src/lib/server/trpc/routes/patient.ts | 13 ++++-- src/lib/shared/model/requests.ts | 4 ++ src/lib/shared/model/validation.ts | 1 + .../patient/[id]/[[query]]/+page.server.ts | 43 +++++++++++-------- .../(app)/patient/[id]/[[query]]/+page.svelte | 18 ++++++-- 12 files changed, 97 insertions(+), 40 deletions(-) diff --git a/src/lib/components/filter/FilterBar.svelte b/src/lib/components/filter/FilterBar.svelte index 6047c78..b7bacfc 100644 --- a/src/lib/components/filter/FilterBar.svelte +++ b/src/lib/components/filter/FilterBar.svelte @@ -21,6 +21,7 @@ export let onUpdate: (filterData: FilterQdata | undefined) => void = () => {}; /** List of hidden filter IDs, can be specified for prefiltered views (e.g. by patient) */ export let hiddenFilters: string[] = []; + /** True if a separate search field should be displayed */ export let search = false; let autocomplete: Autocomplete | undefined; @@ -59,7 +60,9 @@ function updateFromQueryData(filterData: FilterQdata) { const filters: FilterData[] = []; for (const [id, value] of Object.entries(filterData)) { - if (hiddenFilters.includes(id)) continue; + // If filter is hidden or undefined, dont display it + if (hiddenFilters.includes(id) || !FILTERS[id]) continue; + // Extract search parameter if a separate search field is used if (search && id === "search") { searchVal = value.toString(); } else if (Array.isArray(value)) { @@ -184,7 +187,7 @@
-
+
{#each activeFilters as fdata, i} diff --git a/src/lib/components/filter/FilterChip.svelte b/src/lib/components/filter/FilterChip.svelte index 82d9c62..6f9ee0e 100644 --- a/src/lib/components/filter/FilterChip.svelte +++ b/src/lib/components/filter/FilterChip.svelte @@ -32,9 +32,13 @@ autocomplete.open(); } + const TOFF = " aus"; + $: toggleState = fdata.selection?.toggle !== false; - $: filterName = toggleState ? filter.name : filter.toggleOff?.name; - $: filterIcon = toggleState ? filter.icon : filter.toggleOff?.icon; + $: filterName = toggleState + ? filter.name + : filter.toggleOff?.name ?? filter.name + TOFF; + $: filterIcon = toggleState ? filter.icon : filter.toggleOff?.icon ?? filter.icon; $: hasInputField = filter.inputType !== 0 && filter.inputType !== 3; diff --git a/src/lib/components/filter/filters.ts b/src/lib/components/filter/filters.ts index 4966925..6c8cfbf 100644 --- a/src/lib/components/filter/filters.ts +++ b/src/lib/components/filter/filters.ts @@ -2,6 +2,7 @@ import { trpc } from "$lib/shared/trpc"; import { mdiAccount, mdiAccountInjury, + mdiAccountMultipleOutline, mdiAccountRemoveOutline, mdiBedKingOutline, mdiCheckboxBlankOutline, @@ -118,4 +119,10 @@ export const PATIENT_FILTER: { [key: string]: FilterDef } = { icon: mdiAccountRemoveOutline, inputType: 0, }, + includeHidden: { + id: "includeHidden", + name: "Alle anzeigen", + icon: mdiAccountMultipleOutline, + inputType: 0, + }, }; diff --git a/src/lib/components/form/PatientForm.svelte b/src/lib/components/form/PatientForm.svelte index a519fab..d494b9a 100644 --- a/src/lib/components/form/PatientForm.svelte +++ b/src/lib/components/form/PatientForm.svelte @@ -6,6 +6,7 @@ import { superForm, superValidateSync } from "sveltekit-superforms/client"; import type { SuperValidated } from "sveltekit-superforms"; import { ZPatientNew } from "$lib/shared/model/validation"; + import { browser } from "$app/environment"; export let patient: RouterOutput["patient"]["get"] | null = null; export let formData: SuperValidated = @@ -91,11 +92,14 @@
- +
+ + +
diff --git a/src/lib/components/table/PatientField.svelte b/src/lib/components/table/PatientField.svelte index 4de6d23..8aa8597 100644 --- a/src/lib/components/table/PatientField.svelte +++ b/src/lib/components/table/PatientField.svelte @@ -2,7 +2,7 @@ import type { RouterOutput } from "$lib/shared/trpc"; import { gotoEntityQuery } from "$lib/shared/util"; - export let patient: RouterOutput["patient"]["get"]; + export let patient: RouterOutput["patient"]["list"]["items"][0]; export let baseUrl: string; function onClick(e: MouseEvent) { diff --git a/src/lib/components/table/PatientTable.svelte b/src/lib/components/table/PatientTable.svelte index 3c047ae..f9aeda1 100644 --- a/src/lib/components/table/PatientTable.svelte +++ b/src/lib/components/table/PatientTable.svelte @@ -32,7 +32,7 @@ {@const full_name = patient.first_name + " " + patient.last_name} diff --git a/src/lib/server/query/patient.ts b/src/lib/server/query/patient.ts index c8d8256..ae668d0 100644 --- a/src/lib/server/query/patient.ts +++ b/src/lib/server/query/patient.ts @@ -64,6 +64,10 @@ export async function getPatientNames(): Promise { }); } +export async function getPatientNEntries(id: number): Promise { + return prisma.entry.count({ where: { patient_id: id } }); +} + export async function getPatients( filter: PatientsFilter = {}, pagination: PaginationRequest = {}, @@ -86,8 +90,10 @@ export async function getPatients( qb.addOrderClause(`similarity(p.full_name, ${qvar}) desc`); } - if (filter.hidden !== undefined) { - qb.addFilter("p.hidden", filter.hidden); + if (filter.hidden) { + qb.addFilter("p.hidden", true); + } else if (!filter.includeHidden) { + qb.addFilter("p.hidden", false); } qb.addFilterList("r.id", filter.room); diff --git a/src/lib/server/trpc/routes/patient.ts b/src/lib/server/trpc/routes/patient.ts index 971970c..1b19339 100644 --- a/src/lib/server/trpc/routes/patient.ts +++ b/src/lib/server/trpc/routes/patient.ts @@ -1,6 +1,7 @@ import { deletePatient, getPatient, + getPatientNEntries, getPatientNames, getPatients, hidePatient, @@ -13,9 +14,15 @@ import { z } from "zod"; export const patientRouter = t.router({ getNames: t.procedure.query(async () => trpcWrap(getPatientNames)), - get: t.procedure - .input(ZEntityId) - .query(async (opts) => trpcWrap(async () => getPatient(opts.input))), + get: t.procedure.input(ZEntityId).query(async (opts) => + trpcWrap(async () => { + const [patient, n_entries] = await Promise.all([ + getPatient(opts.input), + getPatientNEntries(opts.input), + ]); + return { ...patient, n_entries }; + }) + ), list: t.procedure.input(ZPatientsQuery).query(async (opts) => { return getPatients( opts.input.filter ?? {}, diff --git a/src/lib/shared/model/requests.ts b/src/lib/shared/model/requests.ts index 4daf9a6..5bfbb2b 100644 --- a/src/lib/shared/model/requests.ts +++ b/src/lib/shared/model/requests.ts @@ -29,10 +29,14 @@ export type EntriesFilter = Partial<{ }>; export type PatientsFilter = Partial<{ + /** Search patient names */ search: string; station: FilterList; room: FilterList; + /** Show only hidden patients */ hidden: boolean; + /** Show visible and hidden patients */ + includeHidden: boolean; }>; export type TRPCErrorResponse = { diff --git a/src/lib/shared/model/validation.ts b/src/lib/shared/model/validation.ts index a94d8b8..be33719 100644 --- a/src/lib/shared/model/validation.ts +++ b/src/lib/shared/model/validation.ts @@ -107,6 +107,7 @@ export const ZPatientsFilter = z room: ZFilterList, station: ZFilterList, hidden: z.boolean(), + includeHidden: z.boolean(), }) .partial(); diff --git a/src/routes/(app)/patient/[id]/[[query]]/+page.server.ts b/src/routes/(app)/patient/[id]/[[query]]/+page.server.ts index cd43511..b0ff1b2 100644 --- a/src/routes/(app)/patient/[id]/[[query]]/+page.server.ts +++ b/src/routes/(app)/patient/[id]/[[query]]/+page.server.ts @@ -8,25 +8,34 @@ import { superValidate } from "sveltekit-superforms/server"; export const actions = { default: async (event) => { const id = ZUrlEntityId.parse(event.params.id); - // const form = await event.request.formData(); + const formData = await event.request.formData(); - // const schema = zfd.formData(ZPatientNew.partial()); - // const formData = schema.parse(form); - const form = await superValidate(event.request, ZPatientNew); + const hide = formData.get("hide"); + const del = formData.get("delete"); + if (hide) { + await loadWrap(async () => + trpc(event).patient.hide.mutate({ + id, + hidden: Boolean(parseInt(hide.toString())), + }) + ); + } else if (del) { + await loadWrap(async () => trpc(event).patient.delete.mutate(id)); + } else { + const form = await superValidate(formData, ZPatientNew); - // Convenient validation check: - if (!form.valid) { - // Again, return { form } and things will just work. - return fail(400, { form }); + if (!form.valid) { + return fail(400, { form }); + } + + await loadWrap(async () => + trpc(event).patient.update.mutate({ + id, + patient: form.data, + }) + ); + + return { form }; } - - await loadWrap(async () => - trpc(event).patient.update.mutate({ - id, - patient: form.data, - }) - ); - - return { form }; }, } satisfies Actions; diff --git a/src/routes/(app)/patient/[id]/[[query]]/+page.svelte b/src/routes/(app)/patient/[id]/[[query]]/+page.svelte index 3a65179..2b25680 100644 --- a/src/routes/(app)/patient/[id]/[[query]]/+page.svelte +++ b/src/routes/(app)/patient/[id]/[[query]]/+page.svelte @@ -4,16 +4,28 @@ import PatientForm from "$lib/components/form/PatientForm.svelte"; export let data: PageData; + + $: hasEntries = data.patient.n_entries > 0; Patient #{data.patient.id} - + + {#if data.patient.hidden} + + {:else if hasEntries} + + {:else} + + {/if} + -{#if data.patient.room} -

Einträge

+{#if hasEntries} +

Einträge ({data.patient.n_entries})

Date: Thu, 15 Feb 2024 14:03:55 +0100 Subject: [PATCH 4/4] fix: replace single quotes in ts query string, add ts index --- prisma/migrations/20240113221445_search_index/migration.sql | 1 + src/lib/server/query/util.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/prisma/migrations/20240113221445_search_index/migration.sql b/prisma/migrations/20240113221445_search_index/migration.sql index cd6b5db..01b2f1f 100644 --- a/prisma/migrations/20240113221445_search_index/migration.sql +++ b/prisma/migrations/20240113221445_search_index/migration.sql @@ -33,4 +33,5 @@ EXECUTE PROCEDURE update_entry_tsvec (); ALTER TABLE patients ADD COLUMN full_name TEXT GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED; +CREATE INDEX entries_tsvec ON entries USING GIN (tsvec); CREATE INDEX patients_full_name ON patients USING gin (full_name gin_trgm_ops); diff --git a/src/lib/server/query/util.ts b/src/lib/server/query/util.ts index e1862e3..5f571c0 100644 --- a/src/lib/server/query/util.ts +++ b/src/lib/server/query/util.ts @@ -87,7 +87,7 @@ class SearchQueryComponents { */ export function parseSearchQuery(q: string): SearchQueryComponents { const regexpParts = /(-)?(?:"([^"]*)"|([^"\s]+))(?:\s|$)/g; - const components = Array.from(q.matchAll(regexpParts), (m) => { + const components = Array.from(q.replaceAll("'", '"').matchAll(regexpParts), (m) => { const negative = m[1] === "-"; // Exact if (m[2]) {