Compare commits
4 commits
c2c21d1296
...
6c338e447e
Author | SHA1 | Date | |
---|---|---|---|
6c338e447e | |||
2a7aa618d9 | |||
f630ee08b8 | |||
f485db2968 |
39 changed files with 324 additions and 185 deletions
|
@ -3,6 +3,14 @@
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.2.1](https://code.thetadev.de/HSA/Visitenbuch/compare/v0.2.0..v0.2.1) - 2024-05-07
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- Carta editor mobile context menu transparent - ([6bd7e15](https://code.thetadev.de/HSA/Visitenbuch/commit/6bd7e157eea6589be82692499cb956455386f7e5))
|
||||||
|
- Dont output null age - ([c2c21d1](https://code.thetadev.de/HSA/Visitenbuch/commit/c2c21d1296c399324cdfa661c490cee79f7e16e1))
|
||||||
|
|
||||||
|
|
||||||
## [v0.2.0](https://code.thetadev.de/HSA/Visitenbuch/compare/v0.1.0..v0.2.0) - 2024-05-06
|
## [v0.2.0](https://code.thetadev.de/HSA/Visitenbuch/compare/v0.1.0..v0.2.0) - 2024-05-06
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|
11
package.json
11
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "visitenbuch",
|
"name": "visitenbuch",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
"@floating-ui/core": "^1.6.1",
|
"@floating-ui/core": "^1.6.1",
|
||||||
"@mdi/js": "^7.4.47",
|
"@mdi/js": "^7.4.47",
|
||||||
"@prisma/client": "^5.13.0",
|
"@prisma/client": "^5.13.0",
|
||||||
"carta-md": "4.0.2",
|
"@thetadev/carta-md": "^1.0.1",
|
||||||
"diff": "^5.2.0",
|
"diff": "^5.2.0",
|
||||||
"isomorphic-dompurify": "^2.9.0",
|
"isomorphic-dompurify": "^2.9.0",
|
||||||
"prisma": "^5.13.0",
|
"prisma": "^5.13.0",
|
||||||
|
@ -71,10 +71,5 @@
|
||||||
"vite": "^5.2.11",
|
"vite": "^5.2.11",
|
||||||
"vitest": "^1.6.0"
|
"vitest": "^1.6.0"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module"
|
||||||
"pnpm": {
|
|
||||||
"patchedDependencies": {
|
|
||||||
"carta-md@4.0.2": "patches/carta-md@4.0.2.patch"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
# Change "markdown-body" css class to "prose" for Tailwind compatibility
|
|
||||||
diff --git a/dist/Markdown.svelte b/dist/Markdown.svelte
|
|
||||||
index 92b29fade303a14539720b9bc389e7a41202b1cf..cbdeede16d7af17a481bcac519aea92b8959803d 100644
|
|
||||||
--- a/dist/Markdown.svelte
|
|
||||||
+++ b/dist/Markdown.svelte
|
|
||||||
@@ -15,7 +15,7 @@ onMount(async () => {
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
-<div bind:this={elem} class="carta-viewer carta-theme__{theme} markdown-body">
|
|
||||||
+<div bind:this={elem} class="carta-viewer carta-theme__{theme} prose">
|
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
||||||
{@html rendered}
|
|
||||||
{#if mounted}
|
|
||||||
diff --git a/dist/internal/components/Renderer.svelte b/dist/internal/components/Renderer.svelte
|
|
||||||
index 1d2ff1a6937bc490ea1e6eb5c2ef9f3b33e4c326..6a95c154ea4ca7c4d19b20d02b3504fb2b65b7f7 100644
|
|
||||||
--- a/dist/internal/components/Renderer.svelte
|
|
||||||
+++ b/dist/internal/components/Renderer.svelte
|
|
||||||
@@ -17,7 +17,7 @@ onMount(() => carta.$setRenderer(elem));
|
|
||||||
onMount(() => mounted = true);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
-<div bind:this={elem} on:scroll={handleScroll} class="carta-renderer markdown-body">
|
|
||||||
+<div bind:this={elem} on:scroll={handleScroll} class="carta-renderer prose">
|
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
||||||
{@html renderedHtml}
|
|
||||||
{#if mounted}
|
|
|
@ -4,11 +4,6 @@ settings:
|
||||||
autoInstallPeers: true
|
autoInstallPeers: true
|
||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
patchedDependencies:
|
|
||||||
carta-md@4.0.2:
|
|
||||||
hash: i33ea43vfgrg3ziu25cfu7s2zq
|
|
||||||
path: patches/carta-md@4.0.2.patch
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@auth/core':
|
'@auth/core':
|
||||||
specifier: ^0.30.0
|
specifier: ^0.30.0
|
||||||
|
@ -22,9 +17,9 @@ dependencies:
|
||||||
'@prisma/client':
|
'@prisma/client':
|
||||||
specifier: ^5.13.0
|
specifier: ^5.13.0
|
||||||
version: 5.13.0(prisma@5.13.0)
|
version: 5.13.0(prisma@5.13.0)
|
||||||
carta-md:
|
'@thetadev/carta-md':
|
||||||
specifier: 4.0.2
|
specifier: ^1.0.1
|
||||||
version: 4.0.2(patch_hash=i33ea43vfgrg3ziu25cfu7s2zq)(svelte@4.2.15)
|
version: 1.0.1(svelte@4.2.15)
|
||||||
diff:
|
diff:
|
||||||
specifier: ^5.2.0
|
specifier: ^5.2.0
|
||||||
version: 5.2.0
|
version: 5.2.0
|
||||||
|
@ -889,10 +884,6 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@shikijs/core@1.4.0:
|
|
||||||
resolution: {integrity: sha512-CxpKLntAi64h3j+TwWqVIQObPTED0FyXLHTTh3MKXtqiQNn2JGcMQQ362LftDbc9kYbDtrksNMNoVmVXzKFYUQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@sideway/address@4.1.5:
|
/@sideway/address@4.1.5:
|
||||||
resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==}
|
resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
|
@ -1088,6 +1079,22 @@ packages:
|
||||||
tailwindcss: 3.4.3
|
tailwindcss: 3.4.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@thetadev/carta-md@1.0.1(svelte@4.2.15):
|
||||||
|
resolution: {integrity: sha512-B6TyB5gvrc1T5pEOM5nIZA+N6gG7EK7AJBrQCfokzrpNarq7CvgJEVaQoWh5oukB2MrRYSIQAYUNIoqEKzMSag==}
|
||||||
|
peerDependencies:
|
||||||
|
svelte: ^3.54.0 || ^4.0.0
|
||||||
|
dependencies:
|
||||||
|
prismjs: 1.29.0
|
||||||
|
rehype-stringify: 10.0.0
|
||||||
|
remark-gfm: 4.0.0
|
||||||
|
remark-parse: 11.0.0
|
||||||
|
remark-rehype: 11.1.0
|
||||||
|
svelte: 4.2.15
|
||||||
|
unified: 11.0.4
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@trpc/client@10.45.2(@trpc/server@10.45.2):
|
/@trpc/client@10.45.2(@trpc/server@10.45.2):
|
||||||
resolution: {integrity: sha512-ykALM5kYWTLn1zYuUOZ2cPWlVfrXhc18HzBDyRhoPYN0jey4iQHEFSEowfnhg1RvYnrAVjNBgHNeSAXjrDbGwg==}
|
resolution: {integrity: sha512-ykALM5kYWTLn1zYuUOZ2cPWlVfrXhc18HzBDyRhoPYN0jey4iQHEFSEowfnhg1RvYnrAVjNBgHNeSAXjrDbGwg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1757,23 +1764,6 @@ packages:
|
||||||
resolution: {integrity: sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==}
|
resolution: {integrity: sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/carta-md@4.0.2(patch_hash=i33ea43vfgrg3ziu25cfu7s2zq)(svelte@4.2.15):
|
|
||||||
resolution: {integrity: sha512-wMlw0r5RZiVwvF3dyxE/vHj9pXlXbzpijJ3m/o9zqZe7Cf6D96AjyBHBpa0A0OPj/uEJVF3k0R6ctopBJCpCQg==}
|
|
||||||
peerDependencies:
|
|
||||||
svelte: ^3.54.0 || ^4.0.0
|
|
||||||
dependencies:
|
|
||||||
rehype-stringify: 10.0.0
|
|
||||||
remark-gfm: 4.0.0
|
|
||||||
remark-parse: 11.0.0
|
|
||||||
remark-rehype: 11.1.0
|
|
||||||
shiki: 1.4.0
|
|
||||||
svelte: 4.2.15
|
|
||||||
unified: 11.0.4
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
dev: false
|
|
||||||
patched: true
|
|
||||||
|
|
||||||
/ccount@2.0.1:
|
/ccount@2.0.1:
|
||||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -4424,6 +4414,11 @@ packages:
|
||||||
'@prisma/engines': 5.13.0
|
'@prisma/engines': 5.13.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/prismjs@1.29.0:
|
||||||
|
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/property-expr@2.0.6:
|
/property-expr@2.0.6:
|
||||||
resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==}
|
resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
|
@ -4721,12 +4716,6 @@ packages:
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/shiki@1.4.0:
|
|
||||||
resolution: {integrity: sha512-5WIn0OL8PWm7JhnTwRWXniy6eEDY234mRrERVlFa646V2ErQqwIFd2UML7e0Pq9eqSKLoMa3Ke+xbsF+DAuy+Q==}
|
|
||||||
dependencies:
|
|
||||||
'@shikijs/core': 1.4.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/side-channel@1.0.6:
|
/side-channel@1.0.6:
|
||||||
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
|
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "entry_executions" ADD COLUMN "done" BOOLEAN NOT NULL DEFAULT true;
|
|
@ -142,6 +142,7 @@ model EntryExecution {
|
||||||
entry_id Int
|
entry_id Int
|
||||||
|
|
||||||
text String
|
text String
|
||||||
|
done Boolean @default(true)
|
||||||
|
|
||||||
author User @relation(fields: [author_id], references: [id])
|
author User @relation(fields: [author_id], references: [id])
|
||||||
author_id Int
|
author_id Int
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
<p class="text-sm flex gap-2">
|
<p class="text-sm flex gap-2">
|
||||||
<span>Erstellt {humanDate(entry.created_at, true)}</span>
|
<span>Erstellt {humanDate(entry.created_at, true)}</span>
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
{#if entry.execution}
|
{#if entry.execution?.done}
|
||||||
<span>Erledigt {humanDate(entry.execution.created_at)}</span>
|
<span>Erledigt {humanDate(entry.execution.created_at)}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span>Zu erledigen {humanDate(entry.current_version.date)}</span>
|
<span>Zu erledigen {humanDate(entry.current_version.date)}</span>
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
Beschreibung
|
Beschreibung
|
||||||
<div>
|
<div>
|
||||||
<VersionsButton href="{basePath}/versions" n={entry.n_versions} />
|
<VersionsButton href="{basePath}/versions" n={entry.n_versions} />
|
||||||
<a class="btn btn-circle btn-sm btn-ghost" href="{basePath}/edit">
|
<a class="btn btn-circle btn-xs btn-ghost" href="{basePath}/edit">
|
||||||
<Icon path={mdiPencil} size={1.2} />
|
<Icon path={mdiPencil} size={1.2} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,9 +69,12 @@
|
||||||
|
|
||||||
{#if withExecution && entry.execution}
|
{#if withExecution && entry.execution}
|
||||||
<div class="card2">
|
<div class="card2">
|
||||||
<div class="row c-light text-sm items-center justify-between">
|
<div
|
||||||
|
class="row c-light text-sm items-center justify-between"
|
||||||
|
class:done={entry.execution.done}
|
||||||
|
class:note={!entry.execution.done}>
|
||||||
<p>
|
<p>
|
||||||
Erledigt am {formatDate(entry.execution.created_at, true)} von
|
{entry.execution.done ? "Erledigt am" : "Notiz von"} {formatDate(entry.execution.created_at, true)} von
|
||||||
<UserField filterName="executor" user={entry.execution.author} />
|
<UserField filterName="executor" user={entry.execution.author} />
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
|
@ -88,3 +91,12 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
.done {
|
||||||
|
@apply bg-success/20;
|
||||||
|
}
|
||||||
|
.note {
|
||||||
|
@apply bg-warning/20;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SortRequest } from "$lib/shared/model";
|
|
||||||
import type { RouterOutput } from "$lib/shared/trpc";
|
import type { RouterOutput } from "$lib/shared/trpc";
|
||||||
import { formatDate } from "$lib/shared/util";
|
import { formatDate } from "$lib/shared/util";
|
||||||
|
|
||||||
|
@ -10,8 +9,8 @@
|
||||||
import UserField from "./UserField.svelte";
|
import UserField from "./UserField.svelte";
|
||||||
|
|
||||||
export let entries: RouterOutput["entry"]["list"];
|
export let entries: RouterOutput["entry"]["list"];
|
||||||
export let sortData: SortRequest | undefined;
|
export let sortData: string[] | undefined;
|
||||||
export let sortUpdate: (sort: SortRequest | undefined) => void = () => {};
|
export let sortUpdate: (sort: string[] | undefined) => void = () => {};
|
||||||
export let perPatient = false;
|
export let perPatient = false;
|
||||||
export let baseUrl: string;
|
export let baseUrl: string;
|
||||||
</script>
|
</script>
|
||||||
|
@ -38,7 +37,7 @@
|
||||||
{#each entries.items as entry (entry.id)}
|
{#each entries.items as entry (entry.id)}
|
||||||
<tr
|
<tr
|
||||||
class="transition-colors hover:bg-neutral-content/10"
|
class="transition-colors hover:bg-neutral-content/10"
|
||||||
class:done={entry.execution}
|
class:done={entry.execution?.done}
|
||||||
class:priority={entry.current_version.priority}
|
class:priority={entry.current_version.priority}
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
|
@ -67,7 +66,7 @@
|
||||||
<td><UserField {baseUrl} user={entry.current_version.author} /></td>
|
<td><UserField {baseUrl} user={entry.current_version.author} /></td>
|
||||||
<td><span class="line-clamp-2">{entry.current_version.text}</span></td>
|
<td><span class="line-clamp-2">{entry.current_version.text}</span></td>
|
||||||
<td>
|
<td>
|
||||||
{#if entry.execution}
|
{#if entry.execution?.done}
|
||||||
{formatDate(entry.execution.created_at, true)}
|
{formatDate(entry.execution.created_at, true)}
|
||||||
<UserField
|
<UserField
|
||||||
{baseUrl}
|
{baseUrl}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import type { PaginationRequest, SortRequest } from "$lib/shared/model";
|
import type { PaginationRequest } from "$lib/shared/model";
|
||||||
import type { ZEntriesQuery } from "$lib/shared/model/validation";
|
import type { ZEntriesQuery } from "$lib/shared/model/validation";
|
||||||
import { type RouterOutput } from "$lib/shared/trpc";
|
import { type RouterOutput } from "$lib/shared/trpc";
|
||||||
import { getQueryUrl } from "$lib/shared/util";
|
import { getQueryUrl } from "$lib/shared/util";
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
updateQuery({ filter, sort: query.sort });
|
updateQuery({ filter, sort: query.sort });
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortUpdate(sort: SortRequest | undefined): void {
|
function sortUpdate(sort: string[] | undefined): void {
|
||||||
updateQuery({
|
updateQuery({
|
||||||
filter: query.filter,
|
filter: query.filter,
|
||||||
pagination: query.pagination,
|
pagination: query.pagination,
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import type { PaginationRequest, SortRequest } from "$lib/shared/model";
|
import type { PaginationRequest } from "$lib/shared/model";
|
||||||
import type { ZPatientsQuery } from "$lib/shared/model/validation";
|
import type { ZPatientsQuery } from "$lib/shared/model/validation";
|
||||||
import { type RouterOutput } from "$lib/shared/trpc";
|
import { type RouterOutput } from "$lib/shared/trpc";
|
||||||
import { getQueryUrl } from "$lib/shared/util";
|
import { getQueryUrl } from "$lib/shared/util";
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
updateQuery({ filter, sort: query.sort });
|
updateQuery({ filter, sort: query.sort });
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortUpdate(sort: SortRequest | undefined): void {
|
function sortUpdate(sort: string[] | undefined): void {
|
||||||
updateQuery({
|
updateQuery({
|
||||||
filter: query.filter,
|
filter: query.filter,
|
||||||
pagination: query.pagination,
|
pagination: query.pagination,
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import { mdiFilter } from "@mdi/js";
|
import { mdiFilter } from "@mdi/js";
|
||||||
|
|
||||||
import { URL_ENTRIES } from "$lib/shared/constants";
|
import { URL_ENTRIES } from "$lib/shared/constants";
|
||||||
import type { SortRequest } from "$lib/shared/model";
|
|
||||||
import type { RouterOutput } from "$lib/shared/trpc";
|
import type { RouterOutput } from "$lib/shared/trpc";
|
||||||
import { formatDate, gotoEntityQuery } from "$lib/shared/util";
|
import { formatDate, gotoEntityQuery } from "$lib/shared/util";
|
||||||
|
|
||||||
|
@ -12,8 +11,8 @@
|
||||||
import SortHeader from "./SortHeader.svelte";
|
import SortHeader from "./SortHeader.svelte";
|
||||||
|
|
||||||
export let patients: RouterOutput["patient"]["list"];
|
export let patients: RouterOutput["patient"]["list"];
|
||||||
export let sortData: SortRequest | undefined;
|
export let sortData: string[] | undefined;
|
||||||
export let sortUpdate: (sort: SortRequest | undefined) => void = () => {};
|
export let sortUpdate: (sort: string[] | undefined) => void = () => {};
|
||||||
export let baseUrl: string;
|
export let baseUrl: string;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,33 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { mdiSortAscending, mdiSortDescending } from "@mdi/js";
|
import { mdiSortAscending, mdiSortDescending } from "@mdi/js";
|
||||||
|
|
||||||
import type { SortRequest } from "$lib/shared/model";
|
|
||||||
|
|
||||||
import Icon from "$lib/components/ui/Icon.svelte";
|
import Icon from "$lib/components/ui/Icon.svelte";
|
||||||
|
|
||||||
export let key: string;
|
export let key: string;
|
||||||
export let title: string;
|
export let title: string;
|
||||||
export let sortData: SortRequest | undefined;
|
export let sortData: string[] | undefined;
|
||||||
export let sortUpdate: (sort: SortRequest | undefined) => void = () => {};
|
export let sortUpdate: (sort: string[] | undefined) => void = () => {};
|
||||||
|
|
||||||
// 1: asc, 2: desc, 0: not sorted
|
// 1: asc, 2: desc, 0: not sorted
|
||||||
let sorting = 0;
|
let sorting = 0;
|
||||||
$: if (sortData?.field === key) {
|
$: index = sortData?.findIndex((itm) => itm.split(":", 1)[0] === key) ?? -1;
|
||||||
sorting = sortData.asc !== false ? 1 : 2;
|
$: if (index !== -1) {
|
||||||
|
sorting = sortData![index].split(":", 2)[1] === "dsc" ? 2 : 1;
|
||||||
} else {
|
} else {
|
||||||
sorting = 0;
|
sorting = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClick(): void {
|
function onClick(): void {
|
||||||
if (sorting === 2) {
|
if (!sortData) {
|
||||||
sortUpdate(undefined);
|
sortData = [key];
|
||||||
|
} else if (sorting === 2) {
|
||||||
|
delete sortData[index];
|
||||||
|
} else if (index !== -1) {
|
||||||
|
sortData[index] = sortData[index].split(":", 1) + ":dsc";
|
||||||
} else {
|
} else {
|
||||||
sortUpdate({ field: key, asc: sorting !== 1 });
|
sortData.push(key);
|
||||||
}
|
}
|
||||||
|
sortUpdate(sortData);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -32,6 +36,9 @@
|
||||||
{#if sorting > 0}
|
{#if sorting > 0}
|
||||||
<Icon path={sorting === 1 ? mdiSortAscending : mdiSortDescending} size={1} />
|
<Icon path={sorting === 1 ? mdiSortAscending : mdiSortDescending} size={1} />
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if sortData && sortData.length > 1 && index !== -1}
|
||||||
|
({index + 1})
|
||||||
|
{/if}
|
||||||
{title}
|
{title}
|
||||||
</button>
|
</button>
|
||||||
</th>
|
</th>
|
||||||
|
|
11
src/lib/components/ui/EntryTodoButton.svelte
Normal file
11
src/lib/components/ui/EntryTodoButton.svelte
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<div class="join">
|
||||||
|
{#each { length: 4 } as _, i}
|
||||||
|
<button name="todo" class="join-item btn btn-sm" type="submit" value={i}>
|
||||||
|
{#if i === 0}
|
||||||
|
Notiz
|
||||||
|
{:else}
|
||||||
|
+{i}T
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
|
@ -1,10 +1,15 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Carta, Markdown } from "carta-md";
|
import { Carta } from "@thetadev/carta-md";
|
||||||
|
|
||||||
import { CARTA_CFG } from "./carta";
|
import { CARTA_CFG } from "./carta";
|
||||||
|
|
||||||
export let src: string;
|
export let src: string;
|
||||||
const carta = new Carta(CARTA_CFG);
|
const carta = new Carta(CARTA_CFG);
|
||||||
|
|
||||||
|
$: rendered = carta.renderSSR(src);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Markdown {carta} value={src} />
|
<div class="carta-viewer carta-theme__default prose">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags HTML is sanitized -->
|
||||||
|
{@html rendered}
|
||||||
|
</div>
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "./carta.pcss";
|
import "./carta.pcss";
|
||||||
|
|
||||||
import { Carta, MarkdownEditor, type Labels } from "carta-md";
|
import { Carta, MarkdownEditor, type Labels } from "@thetadev/carta-md";
|
||||||
import type { InputConstraint } from "sveltekit-superforms";
|
import type { InputConstraint } from "sveltekit-superforms";
|
||||||
|
|
||||||
import { CARTA_CFG } from "./carta";
|
import { CARTA_CFG } from "./carta";
|
||||||
|
|
||||||
|
import "@thetadev/carta-md/highlight.css";
|
||||||
|
|
||||||
export let label = "";
|
export let label = "";
|
||||||
export let name = "";
|
export let name = "";
|
||||||
export let value = "";
|
export let value = "";
|
||||||
|
|
|
@ -123,7 +123,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.shiki, .shiki span {
|
.carta-highlight, .carta-highlight span {
|
||||||
color: var(--shiki-dark) !important;
|
color: var(--hl-dark) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Options } from "carta-md";
|
import type { Options } from "@thetadev/carta-md";
|
||||||
|
|
||||||
import { sanitizeHtml } from "$lib/shared/util";
|
import { sanitizeHtml } from "$lib/shared/util";
|
||||||
|
|
||||||
|
|
|
@ -8,15 +8,16 @@ import type {
|
||||||
EntryVersionNew,
|
EntryVersionNew,
|
||||||
Pagination,
|
Pagination,
|
||||||
PaginationRequest,
|
PaginationRequest,
|
||||||
SortRequest,
|
|
||||||
} from "$lib/shared/model";
|
} from "$lib/shared/model";
|
||||||
import { DateRange, normalizeLineEndings, utcDateToYMD } from "$lib/shared/util";
|
import { DateRange, normalizeLineEndings, utcDateToYMD } from "$lib/shared/util";
|
||||||
import { ErrorConflict, ErrorInvalidInput } from "$lib/shared/util/error";
|
import { ErrorConflict } from "$lib/shared/util/error";
|
||||||
|
|
||||||
import { prisma } from "$lib/server/prisma";
|
import { prisma } from "$lib/server/prisma";
|
||||||
|
|
||||||
import { mapEntry, mapVersion, mapExecution } from "./mapping";
|
import { mapEntry, mapVersion, mapExecution } from "./mapping";
|
||||||
import { QueryBuilder, filterListToArray, parseSearchQuery } from "./util";
|
import {
|
||||||
|
QueryBuilder, filterListToArray, mapSortFields, parseSearchQuery,
|
||||||
|
} from "./util";
|
||||||
|
|
||||||
const USER_SELECT = { select: { id: true, name: true } };
|
const USER_SELECT = { select: { id: true, name: true } };
|
||||||
|
|
||||||
|
@ -162,7 +163,7 @@ export async function newEntryExecution(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there are any updates
|
// Check if there are any updates
|
||||||
if (execution.text === cex?.text) {
|
if (execution.text === cex?.text && execution.done === cex?.done) {
|
||||||
return cex.id;
|
return cex.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +172,7 @@ export async function newEntryExecution(
|
||||||
entry_id,
|
entry_id,
|
||||||
author_id,
|
author_id,
|
||||||
text: execution.text,
|
text: execution.text,
|
||||||
|
done: execution.done,
|
||||||
},
|
},
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
});
|
});
|
||||||
|
@ -181,7 +183,7 @@ export async function newEntryExecution(
|
||||||
export async function getEntries(
|
export async function getEntries(
|
||||||
filter: EntriesFilter = {},
|
filter: EntriesFilter = {},
|
||||||
pagination: PaginationRequest = {},
|
pagination: PaginationRequest = {},
|
||||||
sort: SortRequest = { field: "created_at", asc: false },
|
sort: string[] = [],
|
||||||
): Promise<Pagination<Entry>> {
|
): Promise<Pagination<Entry>> {
|
||||||
const qb = new QueryBuilder(
|
const qb = new QueryBuilder(
|
||||||
`select
|
`select
|
||||||
|
@ -199,6 +201,7 @@ export async function getEntries(
|
||||||
c.color as category_color,
|
c.color as category_color,
|
||||||
ex.id as execution_id,
|
ex.id as execution_id,
|
||||||
ex.text as execution_text,
|
ex.text as execution_text,
|
||||||
|
ex.done as execution_done,
|
||||||
ex.created_at as execution_created_at,
|
ex.created_at as execution_created_at,
|
||||||
xau.id as execution_author_id,
|
xau.id as execution_author_id,
|
||||||
xau.name as execution_author_name,
|
xau.name as execution_author_name,
|
||||||
|
@ -254,9 +257,9 @@ left join stations s on s.id = r.station_id`,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter?.done === true) {
|
if (filter?.done === true) {
|
||||||
qb.addFilterClause("ex.id is not null");
|
qb.addFilterClause("ex.done");
|
||||||
} else if (filter?.done === false) {
|
} else if (filter?.done === false) {
|
||||||
qb.addFilterClause("ex.id is null");
|
qb.addFilterClause("(ex.id is null or not ex.done)");
|
||||||
}
|
}
|
||||||
|
|
||||||
qb.addFilterList("xau.id", filter?.executor);
|
qb.addFilterList("xau.id", filter?.executor);
|
||||||
|
@ -299,13 +302,11 @@ left join stations s on s.id = r.station_id`,
|
||||||
date: ["ev.date"],
|
date: ["ev.date"],
|
||||||
author: ["vau.name"],
|
author: ["vau.name"],
|
||||||
executor: ["xau.name"],
|
executor: ["xau.name"],
|
||||||
|
priority: ["ev.priority"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortFields = SORT_FIELDS[sort.field ?? "updated_at"];
|
const sortFields = mapSortFields(sort, "created_at", SORT_FIELDS);
|
||||||
if (!sortFields) {
|
qb.orderByFields(sortFields);
|
||||||
throw new ErrorInvalidInput(`cannot sort by "${sort.field}"`);
|
|
||||||
}
|
|
||||||
qb.orderByFields(sortFields, sort.asc);
|
|
||||||
qb.setPagination(pagination);
|
qb.setPagination(pagination);
|
||||||
|
|
||||||
type RowItem = {
|
type RowItem = {
|
||||||
|
@ -323,6 +324,7 @@ left join stations s on s.id = r.station_id`,
|
||||||
category_color: string;
|
category_color: string;
|
||||||
execution_id: number;
|
execution_id: number;
|
||||||
execution_text: string;
|
execution_text: string;
|
||||||
|
execution_done: boolean;
|
||||||
execution_created_at: Date;
|
execution_created_at: Date;
|
||||||
execution_author_id: number;
|
execution_author_id: number;
|
||||||
execution_author_name: string;
|
execution_author_name: string;
|
||||||
|
@ -383,6 +385,7 @@ left join stations s on s.id = r.station_id`,
|
||||||
id: item.execution_id,
|
id: item.execution_id,
|
||||||
author: { id: item.execution_author_id, name: item.execution_author_name },
|
author: { id: item.execution_author_id, name: item.execution_author_name },
|
||||||
text: item.execution_text,
|
text: item.execution_text,
|
||||||
|
done: item.execution_done,
|
||||||
created_at: item.execution_created_at,
|
created_at: item.execution_created_at,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
|
|
@ -82,6 +82,7 @@ export function mapExecution(execution: DbEntryExecutionLn): EntryExecution {
|
||||||
author: execution.author,
|
author: execution.author,
|
||||||
created_at: execution.created_at,
|
created_at: execution.created_at,
|
||||||
text: execution.text,
|
text: execution.text,
|
||||||
|
done: execution.done,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,12 @@ import type {
|
||||||
PaginationRequest,
|
PaginationRequest,
|
||||||
PatientsFilter,
|
PatientsFilter,
|
||||||
PatientTag,
|
PatientTag,
|
||||||
SortRequest,
|
|
||||||
} from "$lib/shared/model";
|
} from "$lib/shared/model";
|
||||||
import { ErrorInvalidInput } from "$lib/shared/util/error";
|
|
||||||
|
|
||||||
import { prisma } from "$lib/server/prisma";
|
import { prisma } from "$lib/server/prisma";
|
||||||
|
|
||||||
import { mapPatient } from "./mapping";
|
import { mapPatient } from "./mapping";
|
||||||
import { QueryBuilder, handleDeleteConflict } from "./util";
|
import { QueryBuilder, handleDeleteConflict, mapSortFields } from "./util";
|
||||||
|
|
||||||
export async function newPatient(patient: PatientNew): Promise<number> {
|
export async function newPatient(patient: PatientNew): Promise<number> {
|
||||||
const created = await prisma.patient.create({ data: patient, select: { id: true } });
|
const created = await prisma.patient.create({ data: patient, select: { id: true } });
|
||||||
|
@ -69,7 +67,7 @@ export async function getPatientNEntries(id: number): Promise<number> {
|
||||||
export async function getPatients(
|
export async function getPatients(
|
||||||
filter: PatientsFilter = {},
|
filter: PatientsFilter = {},
|
||||||
pagination: PaginationRequest = {},
|
pagination: PaginationRequest = {},
|
||||||
sort: SortRequest = { field: "created_at", asc: false },
|
sort: string[] = [],
|
||||||
): Promise<Pagination<Patient>> {
|
): Promise<Pagination<Patient>> {
|
||||||
const qb = new QueryBuilder(
|
const qb = new QueryBuilder(
|
||||||
`select p.id, p.first_name, p.last_name, p.created_at, p.age, p.hidden,
|
`select p.id, p.first_name, p.last_name, p.created_at, p.age, p.hidden,
|
||||||
|
@ -107,11 +105,8 @@ export async function getPatients(
|
||||||
created_at: ["p.created_at"],
|
created_at: ["p.created_at"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortFields = SORT_FIELDS[sort.field ?? "created_at"];
|
const sortFields = mapSortFields(sort, "created_at", SORT_FIELDS);
|
||||||
if (!sortFields) {
|
qb.orderByFields(sortFields);
|
||||||
throw new ErrorInvalidInput(`cannot sort by "${sort.field}"`);
|
|
||||||
}
|
|
||||||
qb.orderByFields(sortFields, sort.asc);
|
|
||||||
qb.setPagination(pagination);
|
qb.setPagination(pagination);
|
||||||
|
|
||||||
type RowItem = {
|
type RowItem = {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
import { QueryBuilder, parseSearchQuery } from "./util";
|
import { QueryBuilder, mapSortFields, parseSearchQuery } from "./util";
|
||||||
|
|
||||||
test("query builder", () => {
|
test("query builder", () => {
|
||||||
const qb = new QueryBuilder("select e.id, e.text, e.category", "from entries e");
|
const qb = new QueryBuilder("select e.id, e.text, e.category", "from entries e");
|
||||||
|
@ -56,3 +56,19 @@ test("parse search query", () => {
|
||||||
"'hello' <-> 'world' & 'banana' & !'cherry' & !('b' <-> 'x' <-> 'y') & 'vis':*",
|
"'hello' <-> 'world' & 'banana' & !'cherry' & !('b' <-> 'x' <-> 'y') & 'vis':*",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("mapSortFields", () => {
|
||||||
|
const SORT_FIELDS: Record<string, string[]> = {
|
||||||
|
id: ["e.id"],
|
||||||
|
patient: ["p.last_name", "p.first_name"],
|
||||||
|
priority: ["ev.priority"],
|
||||||
|
};
|
||||||
|
const fields = ["id", "patient", "priority:dsc"];
|
||||||
|
const res = mapSortFields(fields, "", SORT_FIELDS);
|
||||||
|
expect(res).toStrictEqual([
|
||||||
|
{ name: "e.id", asc: true },
|
||||||
|
{ name: "p.last_name", asc: true },
|
||||||
|
{ name: "p.first_name", asc: true },
|
||||||
|
{ name: "ev.priority", asc: false },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
||||||
|
|
||||||
import { PAGINATION_LIMIT } from "$lib/shared/constants";
|
import { PAGINATION_LIMIT } from "$lib/shared/constants";
|
||||||
import type { FilterList, PaginationRequest } from "$lib/shared/model";
|
import type { FilterList, PaginationRequest } from "$lib/shared/model";
|
||||||
import { ErrorConflict } from "$lib/shared/util/error";
|
import { ErrorConflict, ErrorInvalidInput } from "$lib/shared/util/error";
|
||||||
|
|
||||||
enum QueryComponentType {
|
enum QueryComponentType {
|
||||||
Normal = 1,
|
Normal = 1,
|
||||||
|
@ -37,6 +37,34 @@ export async function handleDeleteConflict(act: Promise<unknown>, msg: string):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SortField = {
|
||||||
|
name: string,
|
||||||
|
asc: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Map sort fields from request to a list of database columns for the query builder */
|
||||||
|
export function mapSortFields(
|
||||||
|
fields: string[],
|
||||||
|
defaultFilter: string,
|
||||||
|
mapping: Record<string, string[]>,
|
||||||
|
): SortField[] {
|
||||||
|
if (!fields || fields.length === 0) fields = [defaultFilter];
|
||||||
|
|
||||||
|
return fields.flatMap((sf) => {
|
||||||
|
const [fname, fdir] = sf.split(":", 2);
|
||||||
|
const mapped = mapping[fname];
|
||||||
|
if (!mapped) {
|
||||||
|
throw new ErrorInvalidInput(`cannot sort by "${sf}"`);
|
||||||
|
}
|
||||||
|
let asc = true;
|
||||||
|
if (fdir) {
|
||||||
|
if (fdir === "dsc") asc = false;
|
||||||
|
else if (fdir !== "asc") throw new ErrorInvalidInput("Direction must be asc/dsc");
|
||||||
|
}
|
||||||
|
return mapped.map((name) => ({ name, asc }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class SearchQueryComponent {
|
class SearchQueryComponent {
|
||||||
word: string;
|
word: string;
|
||||||
|
|
||||||
|
@ -157,10 +185,12 @@ export class QueryBuilder {
|
||||||
this.orderClauses.push(orderClause);
|
this.orderClauses.push(orderClause);
|
||||||
}
|
}
|
||||||
|
|
||||||
orderByFields(fields: string[], asc: boolean | undefined = undefined): void {
|
orderByFields(fields: SortField[]): void {
|
||||||
const sortDir = asc === false ? " desc" : " asc";
|
const clauseParts = fields.map((f) => {
|
||||||
const orderClause = fields.join(`${sortDir}, `) + sortDir;
|
const sortDir = f.asc ? " asc" : " desc";
|
||||||
this.addOrderClause(orderClause);
|
return f.name + sortDir;
|
||||||
|
});
|
||||||
|
this.addOrderClause(clauseParts.join(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the next parameter variable (e.g. $1) and increment the counter */
|
/** Get the next parameter variable (e.g. $1) and increment the counter */
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const entryRouter = t.router({
|
||||||
.query(async (opts) => trpcWrap(async () => getEntries(
|
.query(async (opts) => trpcWrap(async () => getEntries(
|
||||||
opts.input.filter ?? {},
|
opts.input.filter ?? {},
|
||||||
opts.input.pagination ?? {},
|
opts.input.pagination ?? {},
|
||||||
opts.input.sort ?? {},
|
opts.input.sort ?? [],
|
||||||
))),
|
))),
|
||||||
versions: t.procedure
|
versions: t.procedure
|
||||||
.input(ZEntityId)
|
.input(ZEntityId)
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const patientRouter = t.router({
|
||||||
.query(async (opts) => getPatients(
|
.query(async (opts) => getPatients(
|
||||||
opts.input.filter ?? {},
|
opts.input.filter ?? {},
|
||||||
opts.input.pagination ?? {},
|
opts.input.pagination ?? {},
|
||||||
opts.input.sort ?? {},
|
opts.input.sort ?? [],
|
||||||
)),
|
)),
|
||||||
create: t.procedure
|
create: t.procedure
|
||||||
.input(ZPatientNew)
|
.input(ZPatientNew)
|
||||||
|
|
|
@ -130,11 +130,13 @@ export type EntryExecution = {
|
||||||
id: number;
|
id: number;
|
||||||
author: UserTag;
|
author: UserTag;
|
||||||
text: string;
|
text: string;
|
||||||
|
done: boolean;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EntryExecutionNew = {
|
export type EntryExecutionNew = {
|
||||||
text: string;
|
text: string;
|
||||||
|
done: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SavedFilter = {
|
export type SavedFilter = {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export type EntityQuery = Partial<{
|
export type EntityQuery = Partial<{
|
||||||
filter: EntriesFilter | PatientsFilter;
|
filter: EntriesFilter | PatientsFilter;
|
||||||
pagination: PaginationRequest;
|
pagination: PaginationRequest;
|
||||||
sort: SortRequest;
|
sort: string[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type PaginationRequest = Partial<{
|
export type PaginationRequest = Partial<{
|
||||||
|
@ -9,11 +9,6 @@ export type PaginationRequest = Partial<{
|
||||||
offset: number | undefined;
|
offset: number | undefined;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type SortRequest = Partial<{
|
|
||||||
field: string;
|
|
||||||
asc: boolean;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type FilterList<T> = T | T[] | { id: T; name?: string }[];
|
export type FilterList<T> = T | T[] | { id: T; name?: string }[];
|
||||||
|
|
||||||
export type EntriesFilter = Partial<{
|
export type EntriesFilter = Partial<{
|
||||||
|
|
|
@ -106,7 +106,7 @@ export const ZEntryVersionNew = implement<EntryVersionNew>().with({
|
||||||
export const ZEntryNew = implement<EntryNew>().with({
|
export const ZEntryNew = implement<EntryNew>().with({
|
||||||
patient_id: fields.EntityId(),
|
patient_id: fields.EntityId(),
|
||||||
version: implement<EntryVersionNdata>().with({
|
version: implement<EntryVersionNdata>().with({
|
||||||
text: fields.TextString(),
|
text: z.string(),
|
||||||
date: fields.DateString(),
|
date: fields.DateString(),
|
||||||
category_id: fields.EntityId().nullable(),
|
category_id: fields.EntityId().nullable(),
|
||||||
priority: z.boolean(),
|
priority: z.boolean(),
|
||||||
|
@ -114,7 +114,8 @@ export const ZEntryNew = implement<EntryNew>().with({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZEntryExecutionNew = implement<EntryExecutionNew>().with({
|
export const ZEntryExecutionNew = implement<EntryExecutionNew>().with({
|
||||||
text: fields.TextString(),
|
text: z.string(),
|
||||||
|
done: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZPagination = implement<PaginationRequest>().with({
|
export const ZPagination = implement<PaginationRequest>().with({
|
||||||
|
@ -122,11 +123,6 @@ export const ZPagination = implement<PaginationRequest>().with({
|
||||||
offset: coercedUint.optional(),
|
offset: coercedUint.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZSort = z.object({
|
|
||||||
field: z.string().optional(),
|
|
||||||
asc: coercedBool.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ZFilterListEntry = z.object({
|
const ZFilterListEntry = z.object({
|
||||||
id: coercedUint,
|
id: coercedUint,
|
||||||
name: fields.NameString().optional(),
|
name: fields.NameString().optional(),
|
||||||
|
@ -136,7 +132,7 @@ const paginatedQuery = <T extends z.ZodTypeAny>(f: T) => z
|
||||||
.object({
|
.object({
|
||||||
filter: f,
|
filter: f,
|
||||||
pagination: ZPagination,
|
pagination: ZPagination,
|
||||||
sort: ZSort,
|
sort: z.array(z.string()),
|
||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ export type EntryExecutionChange = {
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
|
|
||||||
text: Diff.Change[];
|
text: Diff.Change[];
|
||||||
|
done?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function newOrUndef<T>(o: T, n: T): T | undefined {
|
function newOrUndef<T>(o: T, n: T): T | undefined {
|
||||||
|
@ -83,6 +84,7 @@ export function executionsDiff(executions: EntryExecution[]): EntryExecutionChan
|
||||||
author: v.author,
|
author: v.author,
|
||||||
created_at: v.created_at,
|
created_at: v.created_at,
|
||||||
text,
|
text,
|
||||||
|
done: v.done,
|
||||||
});
|
});
|
||||||
|
|
||||||
prev = v;
|
prev = v;
|
||||||
|
|
|
@ -16,7 +16,7 @@ it("getQueryUrl", () => {
|
||||||
search: "Hello World",
|
search: "Hello World",
|
||||||
},
|
},
|
||||||
pagination: { limit: 10, offset: 20 },
|
pagination: { limit: 10, offset: 20 },
|
||||||
sort: { field: "room", asc: true },
|
sort: ["room"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryUrl = getQueryUrl(query, "");
|
const queryUrl = getQueryUrl(query, "");
|
||||||
|
|
|
@ -5,13 +5,14 @@ import { TRPCClientError } from "@trpc/client";
|
||||||
import DOMPurify from "isomorphic-dompurify";
|
import DOMPurify from "isomorphic-dompurify";
|
||||||
import qs from "qs";
|
import qs from "qs";
|
||||||
import type { FormOptions } from "sveltekit-superforms";
|
import type { FormOptions } from "sveltekit-superforms";
|
||||||
|
import type { TRPCClientInit } from "trpc-sveltekit";
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
|
|
||||||
import { DEFAULT_FILTER_NAME, URL_VISIT } from "$lib/shared/constants";
|
import { DEFAULT_FILTER_NAME, URL_VISIT } from "$lib/shared/constants";
|
||||||
import type { EntityQuery, SavedFilter } from "$lib/shared/model";
|
import type { EntityQuery, SavedFilter } from "$lib/shared/model";
|
||||||
import { type RouterOutput } from "$lib/shared/trpc";
|
import { trpc, type RouterOutput } from "$lib/shared/trpc";
|
||||||
|
|
||||||
import { DateRange } from "./date";
|
import { DateRange, utcDateToYMD } from "./date";
|
||||||
import { toastError, toastInfo } from "./toast";
|
import { toastError, toastInfo } from "./toast";
|
||||||
|
|
||||||
export function formatBool(val: boolean): string {
|
export function formatBool(val: boolean): string {
|
||||||
|
@ -156,3 +157,19 @@ export function defaultVisitUrl(): string {
|
||||||
},
|
},
|
||||||
}, URL_VISIT);
|
}, URL_VISIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function moveEntryTodoDate(id: number, nTodoDays: number, init?: TRPCClientInit) {
|
||||||
|
if (nTodoDays > 0) {
|
||||||
|
const entry = await trpc(init).entry.get.query(id);
|
||||||
|
const newDate = new Date(entry.current_version.date);
|
||||||
|
newDate.setDate(newDate.getDate() + nTodoDays);
|
||||||
|
|
||||||
|
await trpc(init).entry.newVersion.mutate({
|
||||||
|
id,
|
||||||
|
old_version_id: entry.current_version.id,
|
||||||
|
version: {
|
||||||
|
date: utcDateToYMD(newDate),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,23 +5,35 @@ import { superValidate, message } from "sveltekit-superforms";
|
||||||
|
|
||||||
import { ZUrlEntityId } from "$lib/shared/model/validation";
|
import { ZUrlEntityId } from "$lib/shared/model/validation";
|
||||||
import { trpc } from "$lib/shared/trpc";
|
import { trpc } from "$lib/shared/trpc";
|
||||||
import { loadWrap } from "$lib/shared/util";
|
import { loadWrap, moveEntryTodoDate } from "$lib/shared/util";
|
||||||
|
|
||||||
import { SchemaEntryExecution } from "./schema";
|
import { SchemaNewExecution } from "./editExecution/schema";
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
default: async (event) => loadWrap(async () => {
|
default: async (event) => loadWrap(async () => {
|
||||||
const form = await superValidate(event.request, SchemaEntryExecution);
|
const formData = await event.request.formData();
|
||||||
|
const form = await superValidate(formData, SchemaNewExecution);
|
||||||
if (!form.valid) {
|
if (!form.valid) {
|
||||||
return fail(400, { form });
|
return fail(400, { form });
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = ZUrlEntityId.parse(event.params.id);
|
const id = ZUrlEntityId.parse(event.params.id);
|
||||||
await trpc(event).entry.newExecution.mutate({
|
const todoDays = formData.get("todo");
|
||||||
id,
|
const done = todoDays === null;
|
||||||
old_execution_id: null,
|
const nTodoDays = todoDays ? parseInt(todoDays.toString()) : 0;
|
||||||
execution: { text: form.data.text },
|
|
||||||
|
await loadWrap(async () => {
|
||||||
|
await trpc(event).entry.newExecution.mutate({
|
||||||
|
id,
|
||||||
|
old_execution_id: form.data.old_execution_id,
|
||||||
|
execution: { text: form.data.text, done: todoDays === null },
|
||||||
|
});
|
||||||
|
await moveEntryTodoDate(id, nTodoDays, event);
|
||||||
});
|
});
|
||||||
return message(form, "Eintrag erledigt");
|
|
||||||
|
if (nTodoDays > 0) {
|
||||||
|
return message(form, `Eintrag um ${nTodoDays} Tage in die Zukunft verschoben`);
|
||||||
|
}
|
||||||
|
return message(form, done ? "Eintrag erledigt" : "Eintrag mit Notiz versehen");
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,29 +2,29 @@
|
||||||
|
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
import { defaults, superForm } from "sveltekit-superforms";
|
import { superForm } from "sveltekit-superforms";
|
||||||
|
|
||||||
import { superformConfig } from "$lib/shared/util";
|
import { superformConfig } from "$lib/shared/util";
|
||||||
|
|
||||||
import EntryBody from "$lib/components/entry/EntryBody.svelte";
|
import EntryBody from "$lib/components/entry/EntryBody.svelte";
|
||||||
|
import EntryTodoButton from "$lib/components/ui/EntryTodoButton.svelte";
|
||||||
import MarkdownInput from "$lib/components/ui/markdown/MarkdownInput.svelte";
|
import MarkdownInput from "$lib/components/ui/markdown/MarkdownInput.svelte";
|
||||||
|
|
||||||
import { SchemaEntryExecution } from "./schema";
|
import { SchemaNewExecution } from "./editExecution/schema";
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
const formData = defaults(SchemaEntryExecution);
|
|
||||||
const {
|
const {
|
||||||
form, errors, enhance,
|
form, errors, enhance,
|
||||||
} = superForm(formData, {
|
} = superForm(data.form, {
|
||||||
validators: SchemaEntryExecution,
|
validators: SchemaNewExecution,
|
||||||
...superformConfig("Eintrag"),
|
...superformConfig("Eintrag"),
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<EntryBody entry={data.entry} withExecution />
|
<EntryBody entry={data.entry} withExecution />
|
||||||
|
|
||||||
{#if !data.entry.execution}
|
{#if !data.entry.execution?.done}
|
||||||
<form method="POST" use:enhance>
|
<form method="POST" use:enhance>
|
||||||
<MarkdownInput
|
<MarkdownInput
|
||||||
name="text"
|
name="text"
|
||||||
|
@ -33,9 +33,11 @@
|
||||||
label="Eintrag erledigen"
|
label="Eintrag erledigen"
|
||||||
bind:value={$form.text}
|
bind:value={$form.text}
|
||||||
>
|
>
|
||||||
<div class="row c-vlight">
|
<div class="row c-vlight gap-2">
|
||||||
<button class="btn btn-sm btn-primary" type="submit">Erledigt</button>
|
<button class="btn btn-sm btn-primary" type="submit">Erledigt</button>
|
||||||
|
<EntryTodoButton />
|
||||||
</div>
|
</div>
|
||||||
</MarkdownInput>
|
</MarkdownInput>
|
||||||
|
<input name="old_execution_id" type="hidden" bind:value={$form.old_execution_id} />
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,14 +1,26 @@
|
||||||
import type { PageLoad } from "./$types";
|
import type { PageLoad } from "./$types";
|
||||||
|
|
||||||
|
import { superValidate } from "sveltekit-superforms";
|
||||||
|
|
||||||
import { ZUrlEntityId } from "$lib/shared/model/validation";
|
import { ZUrlEntityId } from "$lib/shared/model/validation";
|
||||||
import { trpc } from "$lib/shared/trpc";
|
import { trpc } from "$lib/shared/trpc";
|
||||||
import { loadWrap } from "$lib/shared/util";
|
import { loadWrap } from "$lib/shared/util";
|
||||||
|
|
||||||
|
import { SchemaNewExecution } from "./editExecution/schema";
|
||||||
|
|
||||||
export const load: PageLoad = async (event) => {
|
export const load: PageLoad = async (event) => {
|
||||||
const entry = await loadWrap(async () => {
|
const entry = await loadWrap(async () => {
|
||||||
const id = ZUrlEntityId.parse(event.params.id);
|
const id = ZUrlEntityId.parse(event.params.id);
|
||||||
return trpc(event).entry.get.query(id);
|
return trpc(event).entry.get.query(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { entry };
|
const form = await superValidate(
|
||||||
|
{
|
||||||
|
old_execution_id: entry.execution?.id,
|
||||||
|
...entry.execution,
|
||||||
|
},
|
||||||
|
SchemaNewExecution,
|
||||||
|
);
|
||||||
|
|
||||||
|
return { entry, form };
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,24 +5,33 @@ import { superValidate } from "sveltekit-superforms";
|
||||||
|
|
||||||
import { ZUrlEntityId } from "$lib/shared/model/validation";
|
import { ZUrlEntityId } from "$lib/shared/model/validation";
|
||||||
import { trpc } from "$lib/shared/trpc";
|
import { trpc } from "$lib/shared/trpc";
|
||||||
import { loadWrap } from "$lib/shared/util";
|
import { loadWrap, moveEntryTodoDate } from "$lib/shared/util";
|
||||||
|
|
||||||
import { SchemaNewExecution } from "./schema";
|
import { SchemaNewExecution } from "./schema";
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
default: async (event) => loadWrap(async () => {
|
default: async (event) => loadWrap(async () => {
|
||||||
const form = await superValidate(event.request, SchemaNewExecution);
|
const formData = await event.request.formData();
|
||||||
|
const form = await superValidate(formData, SchemaNewExecution);
|
||||||
if (!form.valid) {
|
if (!form.valid) {
|
||||||
return fail(400, { form });
|
return fail(400, { form });
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = ZUrlEntityId.parse(event.params.id);
|
const id = ZUrlEntityId.parse(event.params.id);
|
||||||
|
const todoDays = formData.get("todo");
|
||||||
|
|
||||||
await trpc(event).entry.newExecution.mutate({
|
await trpc(event).entry.newExecution.mutate({
|
||||||
id,
|
id,
|
||||||
execution: form.data,
|
execution: {
|
||||||
|
text: form.data.text,
|
||||||
|
done: todoDays === null,
|
||||||
|
},
|
||||||
old_execution_id: form.data.old_execution_id,
|
old_execution_id: form.data.old_execution_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const nTodoDays = todoDays ? parseInt(todoDays.toString()) : 0;
|
||||||
|
await moveEntryTodoDate(id, nTodoDays, event);
|
||||||
|
|
||||||
redirect(302, `/entry/${id}`);
|
redirect(302, `/entry/${id}`);
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,19 +2,19 @@
|
||||||
|
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
import { defaults, superForm } from "sveltekit-superforms";
|
import { superForm } from "sveltekit-superforms";
|
||||||
|
|
||||||
import { superformConfig } from "$lib/shared/util";
|
import { superformConfig } from "$lib/shared/util";
|
||||||
|
|
||||||
import EntryBody from "$lib/components/entry/EntryBody.svelte";
|
import EntryBody from "$lib/components/entry/EntryBody.svelte";
|
||||||
|
import EntryTodoButton from "$lib/components/ui/EntryTodoButton.svelte";
|
||||||
import MarkdownInput from "$lib/components/ui/markdown/MarkdownInput.svelte";
|
import MarkdownInput from "$lib/components/ui/markdown/MarkdownInput.svelte";
|
||||||
|
|
||||||
import { SchemaNewExecution } from "./schema";
|
import { SchemaNewExecution } from "./schema";
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
const formData = defaults(SchemaNewExecution);
|
const { form, errors, enhance } = superForm(data.form, {
|
||||||
const { form, errors, enhance } = superForm(formData, {
|
|
||||||
validators: SchemaNewExecution,
|
validators: SchemaNewExecution,
|
||||||
...superformConfig("Eintrag"),
|
...superformConfig("Eintrag"),
|
||||||
});
|
});
|
||||||
|
@ -30,8 +30,10 @@
|
||||||
label="Ausführungstext bearbeiten"
|
label="Ausführungstext bearbeiten"
|
||||||
bind:value={$form.text}
|
bind:value={$form.text}
|
||||||
>
|
>
|
||||||
<div class="row c-vlight">
|
<div class="row c-vlight gap-2">
|
||||||
<button class="btn btn-sm btn-primary" type="submit">Speichern</button>
|
<button class="btn btn-sm btn-primary" type="submit">Speichern</button>
|
||||||
|
<EntryTodoButton />
|
||||||
</div>
|
</div>
|
||||||
</MarkdownInput>
|
</MarkdownInput>
|
||||||
|
<input name="old_execution_id" type="hidden" bind:value={$form.old_execution_id} />
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -22,6 +22,9 @@
|
||||||
<div class="row c-light text-sm">
|
<div class="row c-light text-sm">
|
||||||
#{i + 1}
|
#{i + 1}
|
||||||
<UserField user={version.author} />, {formatDate(version.created_at, true)}
|
<UserField user={version.author} />, {formatDate(version.created_at, true)}
|
||||||
|
{#if !version.done}
|
||||||
|
<div class="badge ellipsis badge-warning ml-2">Notiz</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if version.text.length > 0}
|
{#if version.text.length > 0}
|
||||||
<div class="rowb whitespace-pre-wrap">
|
<div class="rowb whitespace-pre-wrap">
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const load: PageLoad = async (event) => {
|
||||||
|
|
||||||
// Sort entries by date
|
// Sort entries by date
|
||||||
if (!query.sort) {
|
if (!query.sort) {
|
||||||
query.sort = { field: "date" };
|
query.sort = ["priority:dsc", "date"];
|
||||||
}
|
}
|
||||||
|
|
||||||
const entries = await trpc(event).entry.list.query(query);
|
const entries = await trpc(event).entry.list.query(query);
|
||||||
|
|
|
@ -57,8 +57,8 @@ async function insertTestEntries() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Execute entries
|
// Execute entries
|
||||||
await newEntryExecution(1, eId2, { text: "Some execution txt" });
|
await newEntryExecution(1, eId2, { text: "Some execution txt", done: true });
|
||||||
await newEntryExecution(2, eId3, { text: "More execution txt" });
|
await newEntryExecution(2, eId3, { text: "More execution txt", done: true });
|
||||||
|
|
||||||
return { eId1, eId2, eId3 };
|
return { eId1, eId2, eId3 };
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ test("create entry execution", async () => {
|
||||||
});
|
});
|
||||||
const text = "Blutabnahme erledigt.";
|
const text = "Blutabnahme erledigt.";
|
||||||
|
|
||||||
const xId = await newEntryExecution(1, eId, { text }, null);
|
const xId = await newEntryExecution(1, eId, { text, done: true }, null);
|
||||||
|
|
||||||
const entry = await getEntry(eId);
|
const entry = await getEntry(eId);
|
||||||
expect(entry.execution?.id).toBe(xId);
|
expect(entry.execution?.id).toBe(xId);
|
||||||
|
@ -183,8 +183,8 @@ test("create entry execution (update)", async () => {
|
||||||
version: TEST_VERSION,
|
version: TEST_VERSION,
|
||||||
});
|
});
|
||||||
|
|
||||||
const x1 = await newEntryExecution(1, eId, { text: "x1" }, null);
|
const x1 = await newEntryExecution(1, eId, { text: "x1", done: true }, null);
|
||||||
const x2 = await newEntryExecution(2, eId, { text: "x2" }, x1);
|
const x2 = await newEntryExecution(2, eId, { text: "x2", done: true }, x1);
|
||||||
|
|
||||||
const entry = await getEntry(eId);
|
const entry = await getEntry(eId);
|
||||||
expect(entry.execution?.id).toBe(x2);
|
expect(entry.execution?.id).toBe(x2);
|
||||||
|
@ -200,9 +200,9 @@ test("create entry execution (wrong old xid)", async () => {
|
||||||
patient_id: 1,
|
patient_id: 1,
|
||||||
version: TEST_VERSION,
|
version: TEST_VERSION,
|
||||||
});
|
});
|
||||||
const x1 = await newEntryExecution(1, eId, { text: "x1" }, null);
|
const x1 = await newEntryExecution(1, eId, { text: "x1", done: true }, null);
|
||||||
|
|
||||||
expect(async () => newEntryExecution(1, eId, { text: "x2" }, x1 + 1)).rejects.toThrowError(new ErrorConflict("old execution id does not match"));
|
expect(async () => newEntryExecution(1, eId, { text: "x2", done: true }, x1 + 1)).rejects.toThrowError(new ErrorConflict("old execution id does not match"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("get entries", async () => {
|
test("get entries", async () => {
|
||||||
|
@ -246,8 +246,8 @@ test("get entries", async () => {
|
||||||
const entriesPatient = await getEntries({ patient: 1 }, {});
|
const entriesPatient = await getEntries({ patient: 1 }, {});
|
||||||
expect(entriesPatient.items).length(2);
|
expect(entriesPatient.items).length(2);
|
||||||
expect(entriesPatient.total).toBe(2);
|
expect(entriesPatient.total).toBe(2);
|
||||||
expect(entriesPatient.items[0].id).toBe(eId3);
|
expect(entriesPatient.items[0].id).toBe(eId1);
|
||||||
expect(entriesPatient.items[1].id).toBe(eId1);
|
expect(entriesPatient.items[1].id).toBe(eId3);
|
||||||
|
|
||||||
// Filter by room
|
// Filter by room
|
||||||
const entriesRoom = await getEntries({ room: 1 }, {});
|
const entriesRoom = await getEntries({ room: 1 }, {});
|
||||||
|
@ -257,8 +257,8 @@ test("get entries", async () => {
|
||||||
const entriesDone = await getEntries({ done: true }, {});
|
const entriesDone = await getEntries({ done: true }, {});
|
||||||
expect(entriesDone.items).length(2);
|
expect(entriesDone.items).length(2);
|
||||||
expect(entriesDone.total).toBe(2);
|
expect(entriesDone.total).toBe(2);
|
||||||
expect(entriesDone.items[0].id).toBe(eId3);
|
expect(entriesDone.items[0].id).toBe(eId2);
|
||||||
expect(entriesDone.items[1].id).toBe(eId2);
|
expect(entriesDone.items[1].id).toBe(eId3);
|
||||||
|
|
||||||
// Filter not done
|
// Filter not done
|
||||||
const entriesNotDone = await getEntries({ done: false }, {});
|
const entriesNotDone = await getEntries({ done: false }, {});
|
||||||
|
@ -281,8 +281,8 @@ test("get entries", async () => {
|
||||||
const entriesDateRange = await getEntries({ date: "2024-01-05..2024-01-06" });
|
const entriesDateRange = await getEntries({ date: "2024-01-05..2024-01-06" });
|
||||||
expect(entriesDateRange.items).length(2);
|
expect(entriesDateRange.items).length(2);
|
||||||
expect(entriesDateRange.total).toBe(2);
|
expect(entriesDateRange.total).toBe(2);
|
||||||
expect(entriesDateRange.items[0].id).toBe(eId3);
|
expect(entriesDateRange.items[0].id).toBe(eId2);
|
||||||
expect(entriesDateRange.items[1].id).toBe(eId2);
|
expect(entriesDateRange.items[1].id).toBe(eId3);
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
const entriesSearch = await getEntries({ search: "Blu" }, {});
|
const entriesSearch = await getEntries({ search: "Blu" }, {});
|
||||||
|
@ -293,6 +293,30 @@ test("get entries", async () => {
|
||||||
// NTodo
|
// NTodo
|
||||||
const n = await getNTodo(new Date("2024-01-05"));
|
const n = await getNTodo(new Date("2024-01-05"));
|
||||||
expect(n).toBe(1);
|
expect(n).toBe(1);
|
||||||
|
|
||||||
|
// Sort by ID
|
||||||
|
const entriesSortedId = await getEntries({}, {}, ["id"]);
|
||||||
|
const entriesSortedIdDsc = await getEntries({}, {}, ["id:dsc"]);
|
||||||
|
expect(entriesSortedId.total).toBe(3);
|
||||||
|
expect(entriesSortedIdDsc.total).toBe(3);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
expect(entriesSortedId.items[i]).toStrictEqual(
|
||||||
|
entriesSortedIdDsc.items[entriesSortedIdDsc.items.length - i - 1],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by patient and ID
|
||||||
|
const entriesSortedPatientId = await getEntries({}, {}, ["patient", "id"]);
|
||||||
|
expect(entriesSortedPatientId.items.length).toBe(3);
|
||||||
|
expect(entriesSortedPatientId.items[0].id).toBe(eId1);
|
||||||
|
expect(entriesSortedPatientId.items[1].id).toBe(eId3);
|
||||||
|
expect(entriesSortedPatientId.items[2].id).toBe(eId2);
|
||||||
|
|
||||||
|
const entriesSortedPatientIdDsc = await getEntries({}, {}, ["patient:dsc", "id"]);
|
||||||
|
expect(entriesSortedPatientIdDsc.items.length).toBe(3);
|
||||||
|
expect(entriesSortedPatientIdDsc.items[0].id).toBe(eId2);
|
||||||
|
expect(entriesSortedPatientIdDsc.items[1].id).toBe(eId1);
|
||||||
|
expect(entriesSortedPatientIdDsc.items[2].id).toBe(eId3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("get patient n entries", async () => {
|
test("get patient n entries", async () => {
|
||||||
|
@ -304,3 +328,18 @@ test("get category n entries", async () => {
|
||||||
await insertTestEntries();
|
await insertTestEntries();
|
||||||
expect(await getCategoryNEntries(3)).toBe(1);
|
expect(await getCategoryNEntries(3)).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("todo execution", async () => {
|
||||||
|
const eId = await newEntry(1, {
|
||||||
|
patient_id: 1,
|
||||||
|
version: TEST_VERSION,
|
||||||
|
});
|
||||||
|
|
||||||
|
const n1 = await newEntryExecution(1, eId, { text: "note1", done: false }, null);
|
||||||
|
|
||||||
|
const entry = await getEntry(eId);
|
||||||
|
expect(entry.execution?.id).toBe(n1);
|
||||||
|
expect(entry.execution?.text).toBe("note1");
|
||||||
|
expect(entry.execution?.author.id).toBe(1);
|
||||||
|
expect(entry.execution?.done).toBe(false);
|
||||||
|
});
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
} from "$lib/server/query";
|
} from "$lib/server/query";
|
||||||
import { S1, S2 } from "$tests/helpers/testdata";
|
import { S1, S2 } from "$tests/helpers/testdata";
|
||||||
|
|
||||||
const SORT_ID = { field: "id" };
|
const SORT_ID = ["id"];
|
||||||
|
|
||||||
test("create patient", async () => {
|
test("create patient", async () => {
|
||||||
const pId = await newPatient({
|
const pId = await newPatient({
|
||||||
|
|
Loading…
Reference in a new issue