Compare commits
No commits in common. "152824f6c071b4f647be90d80c680cbd8ce736f3" and "453efb0db70b2886a5322a815db7cfb7f9f87e4c" have entirely different histories.
152824f6c0
...
453efb0db7
23 changed files with 66 additions and 312 deletions
|
@ -1,13 +0,0 @@
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "saved_filter" (
|
|
||||||
"id" SERIAL NOT NULL,
|
|
||||||
"user_id" INTEGER NOT NULL,
|
|
||||||
"view" TEXT NOT NULL,
|
|
||||||
"name" TEXT NOT NULL,
|
|
||||||
"query" TEXT NOT NULL,
|
|
||||||
|
|
||||||
CONSTRAINT "saved_filter_pkey" PRIMARY KEY ("id")
|
|
||||||
);
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "saved_filter" ADD CONSTRAINT "saved_filter_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
@ -40,7 +40,6 @@ model User {
|
||||||
accounts Account[]
|
accounts Account[]
|
||||||
EntryVersion EntryVersion[]
|
EntryVersion EntryVersion[]
|
||||||
EntryExecution EntryExecution[]
|
EntryExecution EntryExecution[]
|
||||||
SavedFilter SavedFilter[]
|
|
||||||
|
|
||||||
@@map("users")
|
@@map("users")
|
||||||
}
|
}
|
||||||
|
@ -81,8 +80,8 @@ model Patient {
|
||||||
|
|
||||||
full_name String? @default(dbgenerated("((first_name || ' '::text) || last_name)")) @ignore
|
full_name String? @default(dbgenerated("((first_name || ' '::text) || last_name)")) @ignore
|
||||||
|
|
||||||
@@index([full_name(ops: raw("gin_trgm_ops"))], map: "patients_full_name", type: Gin)
|
|
||||||
@@map("patients")
|
@@map("patients")
|
||||||
|
@@index([full_name(ops: raw("gin_trgm_ops"))], map: "patients_full_name", type: Gin)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry category (e.g. Blood test, Exams, ...)
|
// Entry category (e.g. Blood test, Exams, ...)
|
||||||
|
@ -108,8 +107,8 @@ model Entry {
|
||||||
|
|
||||||
tsvec Unsupported("tsvector")?
|
tsvec Unsupported("tsvector")?
|
||||||
|
|
||||||
@@index([tsvec], type: Gin)
|
|
||||||
@@map("entries")
|
@@map("entries")
|
||||||
|
@@index([tsvec], type: Gin)
|
||||||
}
|
}
|
||||||
|
|
||||||
model EntryVersion {
|
model EntryVersion {
|
||||||
|
@ -129,8 +128,8 @@ model EntryVersion {
|
||||||
|
|
||||||
created_at DateTime @default(now())
|
created_at DateTime @default(now())
|
||||||
|
|
||||||
@@index([entry_id])
|
|
||||||
@@map("entry_versions")
|
@@map("entry_versions")
|
||||||
|
@@index([entry_id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model EntryExecution {
|
model EntryExecution {
|
||||||
|
@ -145,19 +144,6 @@ model EntryExecution {
|
||||||
|
|
||||||
created_at DateTime @default(now())
|
created_at DateTime @default(now())
|
||||||
|
|
||||||
@@index([entry_id])
|
|
||||||
@@map("entry_executions")
|
@@map("entry_executions")
|
||||||
}
|
@@index([entry_id])
|
||||||
|
|
||||||
model SavedFilter {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
|
|
||||||
user User @relation(fields: [user_id], references: [id])
|
|
||||||
user_id Int
|
|
||||||
|
|
||||||
view String
|
|
||||||
name String
|
|
||||||
query String
|
|
||||||
|
|
||||||
@@map("saved_filter")
|
|
||||||
}
|
}
|
||||||
|
|
36
src/app.d.ts
vendored
36
src/app.d.ts
vendored
|
@ -20,4 +20,40 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "@gramex/url/encode" {
|
||||||
|
export default function encode(obj: unknown, settings?: {
|
||||||
|
listBracket?: boolean;
|
||||||
|
listIndex?: boolean;
|
||||||
|
objBracket?: boolean;
|
||||||
|
sortKeys?: boolean;
|
||||||
|
drop?: unknown[];
|
||||||
|
}): string;
|
||||||
|
|
||||||
|
}
|
||||||
|
declare module "@gramex/url/index" {
|
||||||
|
export { default as encode } from "@gramex/url/encode";
|
||||||
|
import { default as update } from "@gramex/url/update";
|
||||||
|
export { update };
|
||||||
|
export const decode: (url: string, settings?: {
|
||||||
|
convert?: boolean;
|
||||||
|
forceList?: boolean;
|
||||||
|
pruneString?: boolean;
|
||||||
|
}) => unknown;
|
||||||
|
|
||||||
|
}
|
||||||
|
declare module "@gramex/url/update" {
|
||||||
|
export default function update(obj: unknown, url: string, settings?: {
|
||||||
|
convert?: boolean;
|
||||||
|
forceList?: boolean;
|
||||||
|
pruneString?: boolean;
|
||||||
|
pruneObject?: boolean;
|
||||||
|
pruneArray?: boolean;
|
||||||
|
}): unknown;
|
||||||
|
|
||||||
|
}
|
||||||
|
declare module "@gramex/url" {
|
||||||
|
import main = require("@gramex/url/index");
|
||||||
|
export = main;
|
||||||
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
import Autocomplete from "./Autocomplete.svelte";
|
import Autocomplete from "./Autocomplete.svelte";
|
||||||
import EntryFilterChip from "./FilterChip.svelte";
|
import EntryFilterChip from "./FilterChip.svelte";
|
||||||
import SavedFilters from "./SavedFilters.svelte";
|
|
||||||
import type {
|
import type {
|
||||||
FilterDef,
|
FilterDef,
|
||||||
FilterQdata,
|
FilterQdata,
|
||||||
|
@ -28,7 +27,6 @@
|
||||||
export let hiddenFilters: string[] = [];
|
export let hiddenFilters: string[] = [];
|
||||||
/** True if a separate search field should be displayed */
|
/** True if a separate search field should be displayed */
|
||||||
export let search = false;
|
export let search = false;
|
||||||
export let view: string;
|
|
||||||
|
|
||||||
let autocomplete: Autocomplete<BaseItem> | undefined;
|
let autocomplete: Autocomplete<BaseItem> | undefined;
|
||||||
let activeFilters: FilterData[] = [];
|
let activeFilters: FilterData[] = [];
|
||||||
|
@ -248,8 +246,6 @@
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SavedFilters {view} />
|
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
.filterbar-outer {
|
.filterbar-outer {
|
||||||
@apply flex flex-wrap w-full items-start justify-center gap-2 relative;
|
@apply flex flex-wrap w-full items-start justify-center gap-2 relative;
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { mdiClose, mdiFloppy } from "@mdi/js";
|
|
||||||
|
|
||||||
import Icon from "$lib/components/ui/Icon.svelte";
|
|
||||||
|
|
||||||
export let href = "";
|
|
||||||
export let onSave = () => {};
|
|
||||||
export let onRemove = () => {};
|
|
||||||
|
|
||||||
function onSaveInt(e: MouseEvent) {
|
|
||||||
e.preventDefault();
|
|
||||||
onSave();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRemoveInt(e: MouseEvent) {
|
|
||||||
e.preventDefault();
|
|
||||||
onRemove();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<a class="chip btn-transition" {href}>
|
|
||||||
<span class="mx-2">
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<button class="save" on:click={onSaveInt}>
|
|
||||||
<Icon path={mdiFloppy} size={1} />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="remove" on:click={onRemoveInt}>
|
|
||||||
<Icon path={mdiClose} size={1} />
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<style lang="postcss">
|
|
||||||
.chip {
|
|
||||||
@apply flex items-center overflow-hidden rounded-md bg-primary text-primary-content h-8;
|
|
||||||
|
|
||||||
& > button {
|
|
||||||
@apply border-l border-primary-content/10 px-1 flex h-full items-center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.save {
|
|
||||||
@apply hover:bg-success/80 active:bg-success focus:bg-success/80;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove {
|
|
||||||
@apply hover:bg-error/80 active:bg-error focus:bg-error/80;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,82 +0,0 @@
|
||||||
<!-- Bar of saved filter chips -->
|
|
||||||
<script lang="ts">
|
|
||||||
import { mdiPlus } from "@mdi/js";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
|
|
||||||
import type { SavedFilter } from "$lib/shared/model";
|
|
||||||
import { trpc } from "$lib/shared/trpc";
|
|
||||||
|
|
||||||
import Icon from "$lib/components/ui/Icon.svelte";
|
|
||||||
import LoadingIcon from "$lib/components/ui/LoadingIcon.svelte";
|
|
||||||
|
|
||||||
import Chip from "./SavedFilterChip.svelte";
|
|
||||||
|
|
||||||
export let view: string;
|
|
||||||
|
|
||||||
let filters: SavedFilter[] | null = null;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
trpc().savedFilter.get.query(view).then((res) => {
|
|
||||||
filters = res;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function getQuery(): string {
|
|
||||||
return window.location.search.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function create() {
|
|
||||||
const query = getQuery();
|
|
||||||
if (query.length === 0) return;
|
|
||||||
|
|
||||||
const name = prompt("Name");
|
|
||||||
if (!name) return;
|
|
||||||
|
|
||||||
trpc().savedFilter.create.mutate({ name, query, view }).then((id) => {
|
|
||||||
filters?.push({ id, name, query });
|
|
||||||
filters = filters; // force reactive update
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function update(ix: number) {
|
|
||||||
const f = filters![ix];
|
|
||||||
const query = getQuery();
|
|
||||||
if (query.length === 0) return;
|
|
||||||
|
|
||||||
trpc().savedFilter.update.mutate({ id: f.id, query });
|
|
||||||
f.query = query;
|
|
||||||
filters = filters; // force reactive update
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove(ix: number) {
|
|
||||||
const f = filters![ix];
|
|
||||||
trpc().savedFilter.delete.mutate(f.id);
|
|
||||||
filters!.splice(ix, 1);
|
|
||||||
filters = filters;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex flex-row flex-wrap items-center gap-2">
|
|
||||||
<div class="text-sm h-8 flex items-center">
|
|
||||||
Gespeicherte Filter:
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if filters}
|
|
||||||
{#each filters as filter, i (filter.id)}
|
|
||||||
<Chip
|
|
||||||
href={"?" + filter.query}
|
|
||||||
onRemove={() => remove(i)}
|
|
||||||
onSave={() => update(i)}
|
|
||||||
>
|
|
||||||
{filter.name}
|
|
||||||
</Chip>
|
|
||||||
{/each}
|
|
||||||
{:else}
|
|
||||||
<LoadingIcon />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<button class="btn btn-sm btn-primary pl-1" on:click={create}>
|
|
||||||
<Icon path={mdiPlus} />
|
|
||||||
Neu
|
|
||||||
</button>
|
|
||||||
</div>
|
|
|
@ -57,12 +57,9 @@
|
||||||
filterData={query.filter}
|
filterData={query.filter}
|
||||||
hiddenFilters={patientId !== null ? ["patient"] : []}
|
hiddenFilters={patientId !== null ? ["patient"] : []}
|
||||||
onUpdate={filterUpdate}
|
onUpdate={filterUpdate}
|
||||||
view="plan"
|
|
||||||
>
|
>
|
||||||
<slot name="filterbar" />
|
|
||||||
</FilterBar>
|
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
|
</FilterBar>
|
||||||
|
|
||||||
<EntryTable
|
<EntryTable
|
||||||
{baseUrl}
|
{baseUrl}
|
||||||
|
|
|
@ -54,7 +54,6 @@
|
||||||
filterData={query.filter}
|
filterData={query.filter}
|
||||||
onUpdate={filterUpdate}
|
onUpdate={filterUpdate}
|
||||||
search
|
search
|
||||||
view="patients"
|
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</FilterBar>
|
</FilterBar>
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { mdiLoading } from "@mdi/js";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
|
|
||||||
import Icon from "./Icon.svelte";
|
|
||||||
|
|
||||||
let show = false;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
show = false;
|
|
||||||
setTimeout(() => (show = true), 300);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if show}
|
|
||||||
<Icon path={mdiLoading} spin={1} />
|
|
||||||
{/if}
|
|
|
@ -1,28 +0,0 @@
|
||||||
import type { SavedFilter, SavedFilterNew } from "$lib/shared/model";
|
|
||||||
|
|
||||||
import { prisma } from "$lib/server/prisma";
|
|
||||||
|
|
||||||
export async function newSavedFilter(filter: SavedFilterNew, user_id: number): Promise<number> {
|
|
||||||
const created = await prisma.savedFilter.create({
|
|
||||||
data: {
|
|
||||||
user_id,
|
|
||||||
...filter,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return created.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateSavedFilter(id: number, query: string, user_id: number) {
|
|
||||||
await prisma.savedFilter.update({ where: { id, user_id }, data: { query } });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteSavedFilter(id: number, user_id: number) {
|
|
||||||
await prisma.savedFilter.delete({ where: { id, user_id } });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSavedFilters(user_id: number, view: string): Promise<SavedFilter[]> {
|
|
||||||
return prisma.savedFilter.findMany({
|
|
||||||
select: { id: true, name: true, query: true },
|
|
||||||
where: { user_id, view },
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -5,7 +5,6 @@ import { categoryRouter } from "./routes/category";
|
||||||
import { entryRouter } from "./routes/entry";
|
import { entryRouter } from "./routes/entry";
|
||||||
import { patientRouter } from "./routes/patient";
|
import { patientRouter } from "./routes/patient";
|
||||||
import { roomRouter } from "./routes/room";
|
import { roomRouter } from "./routes/room";
|
||||||
import { savedFilterRouter } from "./routes/savedFilter";
|
|
||||||
import { stationRouter } from "./routes/station";
|
import { stationRouter } from "./routes/station";
|
||||||
import { userRouter } from "./routes/user";
|
import { userRouter } from "./routes/user";
|
||||||
|
|
||||||
|
@ -22,7 +21,6 @@ export const router = t.router({
|
||||||
room: roomRouter,
|
room: roomRouter,
|
||||||
patient: patientRouter,
|
patient: patientRouter,
|
||||||
user: userRouter,
|
user: userRouter,
|
||||||
savedFilter: savedFilterRouter,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Router = typeof router;
|
export type Router = typeof router;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ZEntityId, ZCategoryNew } from "$lib/shared/model/validation";
|
import { fields, ZCategoryNew } from "$lib/shared/model/validation";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteCategory,
|
deleteCategory,
|
||||||
|
@ -12,6 +12,8 @@ import {
|
||||||
|
|
||||||
import { t, trpcWrap } from "..";
|
import { t, trpcWrap } from "..";
|
||||||
|
|
||||||
|
const ZEntityId = fields.EntityId();
|
||||||
|
|
||||||
export const categoryRouter = t.router({
|
export const categoryRouter = t.router({
|
||||||
list: t.procedure.query(async () => trpcWrap(getCategories)),
|
list: t.procedure.query(async () => trpcWrap(getCategories)),
|
||||||
get: t.procedure
|
get: t.procedure
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
fields,
|
||||||
ZEntriesQuery,
|
ZEntriesQuery,
|
||||||
ZEntryExecutionNew,
|
ZEntryExecutionNew,
|
||||||
ZEntryNew,
|
ZEntryNew,
|
||||||
ZEntryVersionNew,
|
ZEntryVersionNew,
|
||||||
ZEntityId,
|
|
||||||
} from "$lib/shared/model/validation";
|
} from "$lib/shared/model/validation";
|
||||||
import { executionsDiff, versionsDiff } from "$lib/shared/util/diff";
|
import { executionsDiff, versionsDiff } from "$lib/shared/util/diff";
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ import {
|
||||||
|
|
||||||
import { t, trpcWrap } from "..";
|
import { t, trpcWrap } from "..";
|
||||||
|
|
||||||
|
const ZEntityId = fields.EntityId();
|
||||||
|
|
||||||
export const entryRouter = t.router({
|
export const entryRouter = t.router({
|
||||||
get: t.procedure.input(ZEntityId).query(async (opts) => trpcWrap(async () => {
|
get: t.procedure.input(ZEntityId).query(async (opts) => trpcWrap(async () => {
|
||||||
const [entry, n_versions, n_executions] = await Promise.all([
|
const [entry, n_versions, n_executions] = await Promise.all([
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ZEntityId, ZPatientNew, ZPatientsQuery } from "$lib/shared/model/validation";
|
import { fields, ZPatientNew, ZPatientsQuery } from "$lib/shared/model/validation";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deletePatient,
|
deletePatient,
|
||||||
|
@ -15,6 +15,8 @@ import {
|
||||||
|
|
||||||
import { t, trpcWrap } from "..";
|
import { t, trpcWrap } from "..";
|
||||||
|
|
||||||
|
const ZEntityId = fields.EntityId();
|
||||||
|
|
||||||
export const patientRouter = t.router({
|
export const patientRouter = t.router({
|
||||||
getNames: t.procedure.query(async () => trpcWrap(getPatientNames)),
|
getNames: t.procedure.query(async () => trpcWrap(getPatientNames)),
|
||||||
get: t.procedure.input(ZEntityId).query(async (opts) => trpcWrap(async () => {
|
get: t.procedure.input(ZEntityId).query(async (opts) => trpcWrap(async () => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ZEntityId, ZRoomNew } from "$lib/shared/model/validation";
|
import { fields, ZRoomNew } from "$lib/shared/model/validation";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteRoom, getRoom, getRooms, newRoom, updateRoom,
|
deleteRoom, getRoom, getRooms, newRoom, updateRoom,
|
||||||
|
@ -8,6 +8,8 @@ import {
|
||||||
|
|
||||||
import { t, trpcWrap } from "..";
|
import { t, trpcWrap } from "..";
|
||||||
|
|
||||||
|
const ZEntityId = fields.EntityId();
|
||||||
|
|
||||||
export const roomRouter = t.router({
|
export const roomRouter = t.router({
|
||||||
list: t.procedure.query(async (opts) => trpcWrap(getRooms)),
|
list: t.procedure.query(async (opts) => trpcWrap(getRooms)),
|
||||||
get: t.procedure
|
get: t.procedure
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
import {
|
|
||||||
ZEntityId, ZSavedFilterNew, ZSavedFilterUpdate, fields,
|
|
||||||
} from "$lib/shared/model/validation";
|
|
||||||
|
|
||||||
import {
|
|
||||||
deleteSavedFilter, getSavedFilters, newSavedFilter, updateSavedFilter,
|
|
||||||
} from "$lib/server/query/savedFilter";
|
|
||||||
|
|
||||||
import { t, trpcWrap } from "..";
|
|
||||||
|
|
||||||
export const savedFilterRouter = t.router({
|
|
||||||
get: t.procedure.input(fields.NameString()).query(async (opts) => trpcWrap(
|
|
||||||
async () => getSavedFilters(opts.ctx.user.id, opts.input),
|
|
||||||
)),
|
|
||||||
create: t.procedure.input(ZSavedFilterNew).mutation(async (opts) => trpcWrap(
|
|
||||||
async () => newSavedFilter(opts.input, opts.ctx.user.id),
|
|
||||||
)),
|
|
||||||
update: t.procedure.input(ZSavedFilterUpdate).mutation(async (opts) => trpcWrap(
|
|
||||||
async () => updateSavedFilter(opts.input.id, opts.input.query, opts.ctx.user.id),
|
|
||||||
)),
|
|
||||||
delete: t.procedure.input(ZEntityId).mutation(async (opts) => trpcWrap(
|
|
||||||
async () => deleteSavedFilter(opts.input, opts.ctx.user.id),
|
|
||||||
)),
|
|
||||||
});
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ZEntityId, ZStationNew } from "$lib/shared/model/validation";
|
import { fields, ZStationNew } from "$lib/shared/model/validation";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteStation,
|
deleteStation,
|
||||||
|
@ -12,6 +12,8 @@ import {
|
||||||
|
|
||||||
import { t, trpcWrap } from "..";
|
import { t, trpcWrap } from "..";
|
||||||
|
|
||||||
|
const ZEntityId = fields.EntityId();
|
||||||
|
|
||||||
export const stationRouter = t.router({
|
export const stationRouter = t.router({
|
||||||
list: t.procedure.query(getStations),
|
list: t.procedure.query(getStations),
|
||||||
get: t.procedure
|
get: t.procedure
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ZEntityId, ZPagination } from "$lib/shared/model/validation";
|
import { fields, ZPagination } from "$lib/shared/model/validation";
|
||||||
|
|
||||||
import { getUser, getUserNames, getUsers } from "$lib/server/query";
|
import { getUser, getUserNames, getUsers } from "$lib/server/query";
|
||||||
|
|
||||||
import { t, trpcWrap } from "..";
|
import { t, trpcWrap } from "..";
|
||||||
|
|
||||||
|
const ZEntityId = fields.EntityId();
|
||||||
|
|
||||||
export const userRouter = t.router({
|
export const userRouter = t.router({
|
||||||
list: t.procedure
|
list: t.procedure
|
||||||
.input(z.object({ pagination: ZPagination }).partial())
|
.input(z.object({ pagination: ZPagination }).partial())
|
||||||
|
|
|
@ -127,11 +127,3 @@ export type EntryExecution = {
|
||||||
export type EntryExecutionNew = {
|
export type EntryExecutionNew = {
|
||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SavedFilter = {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
query: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SavedFilterNew = Omit<SavedFilter, "id"> & { view: string };
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
import { ZEntriesFilter, fields } from "./validation";
|
import { fields } from "./validation";
|
||||||
|
|
||||||
test("date string", () => {
|
test("date string", () => {
|
||||||
const DateString = fields.DateString();
|
const DateString = fields.DateString();
|
||||||
|
@ -14,17 +14,3 @@ test("date string", () => {
|
||||||
const dsError = DateString.safeParse("2024-30-10");
|
const dsError = DateString.safeParse("2024-30-10");
|
||||||
expect(dsError.success).toBe(false);
|
expect(dsError.success).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("filter data", () => {
|
|
||||||
// Strings from query params should be converted and order kept
|
|
||||||
const entriesFilter = ZEntriesFilter.parse({
|
|
||||||
done: "true",
|
|
||||||
category: [{ id: "1", name: "CA" }],
|
|
||||||
author: [{ id: "1", name: "Max Mustermann" }],
|
|
||||||
});
|
|
||||||
expect(entriesFilter).toStrictEqual({
|
|
||||||
done: true,
|
|
||||||
category: [{ id: 1, name: "CA" }],
|
|
||||||
author: [{ id: 1, name: "Max Mustermann" }],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import type {
|
||||||
PaginationRequest,
|
PaginationRequest,
|
||||||
PatientNew,
|
PatientNew,
|
||||||
RoomNew,
|
RoomNew,
|
||||||
SavedFilterNew,
|
|
||||||
StationNew,
|
StationNew,
|
||||||
User,
|
User,
|
||||||
} from ".";
|
} from ".";
|
||||||
|
@ -41,27 +40,6 @@ export const fields = {
|
||||||
const coercedUint = z.coerce.number().int().nonnegative();
|
const coercedUint = z.coerce.number().int().nonnegative();
|
||||||
const coercedBool = z.string().toLowerCase().transform((v) => v === "true").or(z.boolean());
|
const coercedBool = z.string().toLowerCase().transform((v) => v === "true").or(z.boolean());
|
||||||
|
|
||||||
function returnDataInSameOrderAsPassed<Schema extends z.ZodObject<z.ZodRawShape>>(
|
|
||||||
schema: Schema,
|
|
||||||
) {
|
|
||||||
return z.any().transform((value, ctx) => {
|
|
||||||
const parsed = schema.safeParse(value);
|
|
||||||
if (parsed.success) {
|
|
||||||
const res: z.infer<Schema> = {};
|
|
||||||
for (const key of Object.keys(value)) {
|
|
||||||
if (key in parsed.data) {
|
|
||||||
// @ts-expect-error keys are safe
|
|
||||||
res[key] = parsed.data[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
} else {
|
|
||||||
parsed.error.issues.forEach((iss) => ctx.addIssue(iss));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ZEntityId = fields.EntityId();
|
|
||||||
export const ZUrlEntityId = coercedUint;
|
export const ZUrlEntityId = coercedUint;
|
||||||
|
|
||||||
export const ZUser = implement<User>().with({
|
export const ZUser = implement<User>().with({
|
||||||
|
@ -138,7 +116,7 @@ const paginatedQuery = <T extends z.ZodTypeAny>(f: T) => z
|
||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|
||||||
export const ZEntriesFilter = returnDataInSameOrderAsPassed(z
|
export const ZEntriesFilter = z
|
||||||
.object({
|
.object({
|
||||||
author: ZFilterList,
|
author: ZFilterList,
|
||||||
category: ZFilterList,
|
category: ZFilterList,
|
||||||
|
@ -150,11 +128,11 @@ export const ZEntriesFilter = returnDataInSameOrderAsPassed(z
|
||||||
search: z.string(),
|
search: z.string(),
|
||||||
station: ZFilterList,
|
station: ZFilterList,
|
||||||
})
|
})
|
||||||
.partial());
|
.partial();
|
||||||
|
|
||||||
export const ZEntriesQuery = paginatedQuery(ZEntriesFilter);
|
export const ZEntriesQuery = paginatedQuery(ZEntriesFilter);
|
||||||
|
|
||||||
export const ZPatientsFilter = returnDataInSameOrderAsPassed(z
|
export const ZPatientsFilter = z
|
||||||
.object({
|
.object({
|
||||||
search: z.string(),
|
search: z.string(),
|
||||||
room: ZFilterList,
|
room: ZFilterList,
|
||||||
|
@ -162,17 +140,6 @@ export const ZPatientsFilter = returnDataInSameOrderAsPassed(z
|
||||||
hidden: coercedBool,
|
hidden: coercedBool,
|
||||||
includeHidden: z.coerce.boolean(),
|
includeHidden: z.coerce.boolean(),
|
||||||
})
|
})
|
||||||
.partial());
|
.partial();
|
||||||
|
|
||||||
export const ZPatientsQuery = paginatedQuery(ZPatientsFilter);
|
export const ZPatientsQuery = paginatedQuery(ZPatientsFilter);
|
||||||
|
|
||||||
export const ZSavedFilterNew = implement<SavedFilterNew>().with({
|
|
||||||
view: fields.NameString(),
|
|
||||||
name: fields.NameString(),
|
|
||||||
query: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZSavedFilterUpdate = z.object({
|
|
||||||
id: ZEntityId,
|
|
||||||
query: z.string(),
|
|
||||||
});
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ test("versions diff", () => {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "Laborabnahme",
|
name: "Laborabnahme",
|
||||||
description: "Blutabnahme zur Untersuchung im Labor",
|
description: "Blutabnahme zur Untersuchung im Labor",
|
||||||
color: "#FF0000",
|
color: "FF0000",
|
||||||
},
|
},
|
||||||
created_at: new Date("2024-02-10T11:31:00.000Z"),
|
created_at: new Date("2024-02-10T11:31:00.000Z"),
|
||||||
date: "2024-01-12",
|
date: "2024-01-12",
|
||||||
|
|
|
@ -13,5 +13,5 @@
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<FilteredEntryTable baseUrl={URL_ENTRIES} entries={data.entries} query={data.query}>
|
<FilteredEntryTable baseUrl={URL_ENTRIES} entries={data.entries} query={data.query}>
|
||||||
<a slot="filterbar" class="btn btn-sm btn-primary" href="/entry/new">Neuer Eintrag</a>
|
<a class="btn btn-sm btn-primary" href="/entry/new">Neuer Eintrag</a>
|
||||||
</FilteredEntryTable>
|
</FilteredEntryTable>
|
||||||
|
|
Loading…
Reference in a new issue