Compare commits
No commits in common. "0ae735f2860919dffb957dbc7f556865cd73237e" and "799dbc4097b5cf72e813e67b6ca6ccfe6ab5f08b" have entirely different histories.
0ae735f286
...
799dbc4097
100 changed files with 464 additions and 1416 deletions
41
CHANGELOG.md
41
CHANGELOG.md
|
@ -1,41 +0,0 @@
|
||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
|
|
||||||
## [v0.2.0](https://code.thetadev.de/HSA/Visitenbuch/compare/v0.1.0..v0.2.0) - 2024-05-06
|
|
||||||
|
|
||||||
### 🚀 Features
|
|
||||||
|
|
||||||
- Add toast error messages - ([80c6243](https://code.thetadev.de/HSA/Visitenbuch/commit/80c6243e2b1fe1c8c92b57809c094219be666065))
|
|
||||||
- Improve week selector, select all TODO items by default - ([21f145f](https://code.thetadev.de/HSA/Visitenbuch/commit/21f145f5f08e4f44beecd3d373dfc69e5fc94f23))
|
|
||||||
- Use global store for saved filters, add default filters - ([f36ae71](https://code.thetadev.de/HSA/Visitenbuch/commit/f36ae71d32ebcfc5054dca9c6907ffc1554f39e3))
|
|
||||||
- Hide rooms/stations/categories - ([2cb8dce](https://code.thetadev.de/HSA/Visitenbuch/commit/2cb8dce51a880d46af3a77ee9cbdfce680b88755))
|
|
||||||
- Add about page, licenses - ([5230ee3](https://code.thetadev.de/HSA/Visitenbuch/commit/5230ee375c7b25e577dc2ae045309d8cd54cfdf4))
|
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
|
||||||
|
|
||||||
- Date timezone issue, refactor utils - ([efb0e24](https://code.thetadev.de/HSA/Visitenbuch/commit/efb0e246127489d618ca6e3ea50f10dd1140a954))
|
|
||||||
- Normalize line endings - ([799dbc4](https://code.thetadev.de/HSA/Visitenbuch/commit/799dbc4097b5cf72e813e67b6ca6ccfe6ab5f08b))
|
|
||||||
- Light/dark theme - ([8630b6a](https://code.thetadev.de/HSA/Visitenbuch/commit/8630b6a32d850e93ff1b426a92f68400f7b2f666))
|
|
||||||
- Not using UTC dates for parsing date ranges in backend - ([519ae01](https://code.thetadev.de/HSA/Visitenbuch/commit/519ae01eeec294f7522c5ad9bcf8f2b6b44539fb))
|
|
||||||
|
|
||||||
### 🚜 Refactor
|
|
||||||
|
|
||||||
- Added return types - ([e6302f3](https://code.thetadev.de/HSA/Visitenbuch/commit/e6302f380b903e3ab3f4ef6b5c6e1483d2939027))
|
|
||||||
|
|
||||||
### ⚙️ Miscellaneous Tasks
|
|
||||||
|
|
||||||
- Update dependencies - ([e3f7341](https://code.thetadev.de/HSA/Visitenbuch/commit/e3f7341a0e575a9b0cb922c9b697c21f6f6875c6))
|
|
||||||
- Add git-cliff - ([1b96a46](https://code.thetadev.de/HSA/Visitenbuch/commit/1b96a46dcf2b34318bed5fc383a839a6d7cfd25e))
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
- Remove docker multistage build, add entrypoint - ([be52e70](https://code.thetadev.de/HSA/Visitenbuch/commit/be52e70e8d5abed676c9594f2de0e749b7d2da08))
|
|
||||||
|
|
||||||
|
|
||||||
## [v0.1.0](https://code.thetadev.de/HSA/Visitenbuch/commits/tag/v0.1.0) - 2024-05-06
|
|
||||||
|
|
||||||
Initial release
|
|
||||||
|
|
||||||
<!-- generated by git-cliff -->
|
|
100
cliff.toml
100
cliff.toml
|
@ -1,100 +0,0 @@
|
||||||
# git-cliff ~ default configuration file
|
|
||||||
# https://git-cliff.org/docs/configuration
|
|
||||||
#
|
|
||||||
# Lines starting with "#" are comments.
|
|
||||||
# Configuration options are organized into tables and keys.
|
|
||||||
# See documentation for more information on available options.
|
|
||||||
|
|
||||||
[changelog]
|
|
||||||
# changelog header
|
|
||||||
header = """
|
|
||||||
# Changelog\n
|
|
||||||
All notable changes to this project will be documented in this file.\n
|
|
||||||
"""
|
|
||||||
# template for the changelog body
|
|
||||||
# https://keats.github.io/tera/docs/#introduction
|
|
||||||
body = """
|
|
||||||
{% set repo_url = "https://code.thetadev.de/HSA/Visitenbuch" %}\
|
|
||||||
{% if version %}\
|
|
||||||
{%set vname = version | split(pat="/") | last %}
|
|
||||||
{%if previous.version %}\
|
|
||||||
## [{{ vname }}]({{ repo_url }}/compare/{{ previous.version }}..{{ version }})\
|
|
||||||
{% else %}\
|
|
||||||
## [{{ vname }}]({{ repo_url }}/commits/tag/{{ version }})\
|
|
||||||
{% endif %} - {{ timestamp | date(format="%Y-%m-%d") }}
|
|
||||||
{% else %}\
|
|
||||||
## [unreleased]
|
|
||||||
{% endif %}\
|
|
||||||
{% if previous.version %}\
|
|
||||||
{% for group, commits in commits | group_by(attribute="group") %}
|
|
||||||
### {{ group | striptags | trim | upper_first }}
|
|
||||||
{% for commit in commits %}
|
|
||||||
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
|
|
||||||
{% if commit.breaking %}[**breaking**] {% endif %}\
|
|
||||||
{{ commit.message | upper_first }} - \
|
|
||||||
([{{ commit.id | truncate(length=7, end="") }}]({{ repo_url }}/commit/{{ commit.id }}))\
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}\
|
|
||||||
{% else %}
|
|
||||||
Initial release
|
|
||||||
{% endif %}\n
|
|
||||||
"""
|
|
||||||
# template for the changelog footer
|
|
||||||
footer = """
|
|
||||||
<!-- generated by git-cliff -->
|
|
||||||
"""
|
|
||||||
# remove the leading and trailing s
|
|
||||||
trim = true
|
|
||||||
# postprocessors
|
|
||||||
postprocessors = [
|
|
||||||
# { pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL
|
|
||||||
]
|
|
||||||
|
|
||||||
[git]
|
|
||||||
# parse the commits based on https://www.conventionalcommits.org
|
|
||||||
conventional_commits = true
|
|
||||||
# filter out the commits that are not conventional
|
|
||||||
filter_unconventional = true
|
|
||||||
# process each line of a commit as an individual commit
|
|
||||||
split_commits = false
|
|
||||||
# regex for preprocessing the commit messages
|
|
||||||
commit_preprocessors = [
|
|
||||||
# Replace issue numbers
|
|
||||||
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
|
|
||||||
# Check spelling of the commit with https://github.com/crate-ci/typos
|
|
||||||
# If the spelling is incorrect, it will be automatically fixed.
|
|
||||||
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
|
|
||||||
]
|
|
||||||
# regex for parsing and grouping commits
|
|
||||||
commit_parsers = [
|
|
||||||
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
|
|
||||||
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
|
|
||||||
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
|
|
||||||
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
|
|
||||||
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
|
|
||||||
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
|
|
||||||
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
|
|
||||||
{ message = "^chore\\(release\\)", skip = true },
|
|
||||||
{ message = "^chore\\(pr\\)", skip = true },
|
|
||||||
{ message = "^chore\\(pull\\)", skip = true },
|
|
||||||
{ message = "^chore", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
|
|
||||||
{ message = "^ci", skip = true },
|
|
||||||
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
|
|
||||||
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
|
|
||||||
]
|
|
||||||
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
|
||||||
protect_breaking_commits = false
|
|
||||||
# filter out the commits that are not matched by commit parsers
|
|
||||||
filter_commits = false
|
|
||||||
# regex for matching git tags
|
|
||||||
# tag_pattern = "v[0-9].*"
|
|
||||||
# regex for skipping tags
|
|
||||||
# skip_tags = ""
|
|
||||||
# regex for ignoring tags
|
|
||||||
# ignore_tags = ""
|
|
||||||
# sort the tags topologically
|
|
||||||
topo_order = false
|
|
||||||
# sort the commits inside sections by oldest/newest order
|
|
||||||
sort_commits = "oldest"
|
|
||||||
# limit the number of commits included in the changelog.
|
|
||||||
# limit_commits = 42
|
|
|
@ -493,7 +493,7 @@ export default [
|
||||||
|
|
||||||
// specify whether double or single quotes should be used
|
// specify whether double or single quotes should be used
|
||||||
// https://eslint.style/rules/default/quotes
|
// https://eslint.style/rules/default/quotes
|
||||||
"@stylistic/quotes": ["warn", "double", { avoidEscape: true, allowTemplateLiterals: true }],
|
"@stylistic/quotes": ["warn", "double", { avoidEscape: true }],
|
||||||
|
|
||||||
// require or disallow use of semicolons instead of ASI
|
// require or disallow use of semicolons instead of ASI
|
||||||
// https://eslint.style/rules/default/semi
|
// https://eslint.style/rules/default/semi
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "visitenbuch",
|
"name": "visitenbuch",
|
||||||
"version": "0.2.0",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -45,7 +45,6 @@
|
||||||
"@types/node": "^20.12.8",
|
"@types/node": "^20.12.8",
|
||||||
"@types/qs": "^6.9.15",
|
"@types/qs": "^6.9.15",
|
||||||
"@types/set-cookie-parser": "^2.4.7",
|
"@types/set-cookie-parser": "^2.4.7",
|
||||||
"@zerodevx/svelte-toast": "^0.9.5",
|
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"daisyui": "^4.10.5",
|
"daisyui": "^4.10.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
|
@ -58,7 +57,6 @@
|
||||||
"globals": "^15.1.0",
|
"globals": "^15.1.0",
|
||||||
"postcss-import": "^16.1.0",
|
"postcss-import": "^16.1.0",
|
||||||
"postcss-nesting": "^12.1.2",
|
"postcss-nesting": "^12.1.2",
|
||||||
"rollup-license-plugin": "^3.0.0",
|
|
||||||
"svelte": "^4.2.15",
|
"svelte": "^4.2.15",
|
||||||
"svelte-check": "^3.7.1",
|
"svelte-check": "^3.7.1",
|
||||||
"sveltekit-superforms": "^2.13.0",
|
"sveltekit-superforms": "^2.13.0",
|
||||||
|
|
|
@ -90,9 +90,6 @@ devDependencies:
|
||||||
'@types/set-cookie-parser':
|
'@types/set-cookie-parser':
|
||||||
specifier: ^2.4.7
|
specifier: ^2.4.7
|
||||||
version: 2.4.7
|
version: 2.4.7
|
||||||
'@zerodevx/svelte-toast':
|
|
||||||
specifier: ^0.9.5
|
|
||||||
version: 0.9.5(svelte@4.2.15)
|
|
||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.4.19
|
specifier: ^10.4.19
|
||||||
version: 10.4.19(postcss@8.4.38)
|
version: 10.4.19(postcss@8.4.38)
|
||||||
|
@ -129,9 +126,6 @@ devDependencies:
|
||||||
postcss-nesting:
|
postcss-nesting:
|
||||||
specifier: ^12.1.2
|
specifier: ^12.1.2
|
||||||
version: 12.1.2(postcss@8.4.38)
|
version: 12.1.2(postcss@8.4.38)
|
||||||
rollup-license-plugin:
|
|
||||||
specifier: ^3.0.0
|
|
||||||
version: 3.0.0
|
|
||||||
svelte:
|
svelte:
|
||||||
specifier: ^4.2.15
|
specifier: ^4.2.15
|
||||||
version: 4.2.15
|
version: 4.2.15
|
||||||
|
@ -1454,14 +1448,6 @@ packages:
|
||||||
pretty-format: 29.7.0
|
pretty-format: 29.7.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@zerodevx/svelte-toast@0.9.5(svelte@4.2.15):
|
|
||||||
resolution: {integrity: sha512-JLeB/oRdJfT+dz9A5bgd3Z7TuQnBQbeUtXrGIrNWMGqWbabpepBF2KxtWVhL2qtxpRqhae2f6NAOzH7xs4jUSw==}
|
|
||||||
peerDependencies:
|
|
||||||
svelte: ^3.57.0 || ^4.0.0
|
|
||||||
dependencies:
|
|
||||||
svelte: 4.2.15
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/acorn-jsx@5.3.2(acorn@8.11.3):
|
/acorn-jsx@5.3.2(acorn@8.11.3):
|
||||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1937,11 +1923,6 @@ packages:
|
||||||
- postcss
|
- postcss
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/data-uri-to-buffer@4.0.1:
|
|
||||||
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
|
||||||
engines: {node: '>= 12'}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/data-urls@5.0.0:
|
/data-urls@5.0.0:
|
||||||
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
|
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
@ -2603,14 +2584,6 @@ packages:
|
||||||
reusify: 1.0.4
|
reusify: 1.0.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/fetch-blob@3.2.0:
|
|
||||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
|
||||||
engines: {node: ^12.20 || >= 14.13}
|
|
||||||
dependencies:
|
|
||||||
node-domexception: 1.0.0
|
|
||||||
web-streams-polyfill: 3.3.3
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/file-entry-cache@6.0.1:
|
/file-entry-cache@6.0.1:
|
||||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||||
engines: {node: ^10.12.0 || >=12.0.0}
|
engines: {node: ^10.12.0 || >=12.0.0}
|
||||||
|
@ -2669,13 +2642,6 @@ packages:
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/formdata-polyfill@4.0.10:
|
|
||||||
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
|
|
||||||
engines: {node: '>=12.20.0'}
|
|
||||||
dependencies:
|
|
||||||
fetch-blob: 3.2.0
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/fraction.js@4.3.7:
|
/fraction.js@4.3.7:
|
||||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -2731,11 +2697,6 @@ packages:
|
||||||
has-symbols: 1.0.3
|
has-symbols: 1.0.3
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
|
|
||||||
/get-npm-tarball-url@2.1.0:
|
|
||||||
resolution: {integrity: sha512-ro+DiMu5DXgRBabqXupW38h7WPZ9+Ad8UjwhvsmmN8w1sU7ab0nzAXvVZ4kqYg57OrqomRtJvepX5/xvFKNtjA==}
|
|
||||||
engines: {node: '>=12.17'}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/get-stream@8.0.1:
|
/get-stream@8.0.1:
|
||||||
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
|
@ -3977,20 +3938,6 @@ packages:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/node-domexception@1.0.0:
|
|
||||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
|
||||||
engines: {node: '>=10.5.0'}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/node-fetch@3.3.2:
|
|
||||||
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
|
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
|
||||||
dependencies:
|
|
||||||
data-uri-to-buffer: 4.0.1
|
|
||||||
fetch-blob: 3.2.0
|
|
||||||
formdata-polyfill: 4.0.10
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/node-releases@2.0.14:
|
/node-releases@2.0.14:
|
||||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -4581,15 +4528,6 @@ packages:
|
||||||
glob: 7.2.3
|
glob: 7.2.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/rollup-license-plugin@3.0.0:
|
|
||||||
resolution: {integrity: sha512-dcTdmq+vgqNrSq8Q7uXWp+49u8Mq4gDGeUUI92/4+STMYDyJmO+H+WFLq8DGLWECZ8ckUYAzlfbOyfmbHBE68w==}
|
|
||||||
engines: {node: '>=18.0.0'}
|
|
||||||
dependencies:
|
|
||||||
get-npm-tarball-url: 2.1.0
|
|
||||||
node-fetch: 3.3.2
|
|
||||||
spdx-expression-validate: 2.0.0
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/rollup@4.17.2:
|
/rollup@4.17.2:
|
||||||
resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==}
|
resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==}
|
||||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
|
@ -4791,27 +4729,6 @@ packages:
|
||||||
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/spdx-exceptions@2.5.0:
|
|
||||||
resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/spdx-expression-parse@3.0.1:
|
|
||||||
resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
|
|
||||||
dependencies:
|
|
||||||
spdx-exceptions: 2.5.0
|
|
||||||
spdx-license-ids: 3.0.17
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/spdx-expression-validate@2.0.0:
|
|
||||||
resolution: {integrity: sha512-b3wydZLM+Tc6CFvaRDBOF9d76oGIHNCLYFeHbftFXUWjnfZWganmDmvtM5sm1cRwJc/VDBMLyGGrsLFd1vOxbg==}
|
|
||||||
dependencies:
|
|
||||||
spdx-expression-parse: 3.0.1
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/spdx-license-ids@3.0.17:
|
|
||||||
resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/stackback@0.0.2:
|
/stackback@0.0.2:
|
||||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -5654,11 +5571,6 @@ packages:
|
||||||
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
|
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/web-streams-polyfill@3.3.3:
|
|
||||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
|
||||||
engines: {node: '>= 8'}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/webidl-conversions@7.0.0:
|
/webidl-conversions@7.0.0:
|
||||||
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
-- DropForeignKey
|
|
||||||
ALTER TABLE "entry_versions" DROP CONSTRAINT "entry_versions_category_id_fkey";
|
|
||||||
|
|
||||||
-- DropForeignKey
|
|
||||||
ALTER TABLE "patients" DROP CONSTRAINT "patients_room_id_fkey";
|
|
||||||
|
|
||||||
-- DropForeignKey
|
|
||||||
ALTER TABLE "rooms" DROP CONSTRAINT "rooms_station_id_fkey";
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "categories" ADD COLUMN "hidden" BOOLEAN NOT NULL DEFAULT false;
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "rooms" ADD COLUMN "hidden" BOOLEAN NOT NULL DEFAULT false;
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "stations" ADD COLUMN "hidden" BOOLEAN NOT NULL DEFAULT false;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "rooms" ADD CONSTRAINT "rooms_station_id_fkey" FOREIGN KEY ("station_id") REFERENCES "stations"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "patients" ADD CONSTRAINT "patients_room_id_fkey" FOREIGN KEY ("room_id") REFERENCES "rooms"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- AddForeignKey
|
|
||||||
ALTER TABLE "entry_versions" ADD CONSTRAINT "entry_versions_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "categories"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
|
@ -49,10 +49,9 @@ model User {
|
||||||
|
|
||||||
// Hospital station
|
// Hospital station
|
||||||
model Station {
|
model Station {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String
|
name String
|
||||||
Room Room[]
|
Room Room[]
|
||||||
hidden Boolean @default(false)
|
|
||||||
|
|
||||||
@@map("stations")
|
@@map("stations")
|
||||||
}
|
}
|
||||||
|
@ -61,10 +60,9 @@ model Station {
|
||||||
model Room {
|
model Room {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String
|
name String
|
||||||
station Station @relation(fields: [station_id], references: [id], onDelete: Restrict)
|
station Station @relation(fields: [station_id], references: [id], onDelete: Cascade)
|
||||||
station_id Int
|
station_id Int
|
||||||
Patient Patient[]
|
Patient Patient[]
|
||||||
hidden Boolean @default(false)
|
|
||||||
|
|
||||||
@@map("rooms")
|
@@map("rooms")
|
||||||
}
|
}
|
||||||
|
@ -74,7 +72,7 @@ model Patient {
|
||||||
first_name String
|
first_name String
|
||||||
last_name String
|
last_name String
|
||||||
age Int?
|
age Int?
|
||||||
room Room? @relation(fields: [room_id], references: [id], onDelete: Restrict)
|
room Room? @relation(fields: [room_id], references: [id], onDelete: SetNull)
|
||||||
room_id Int?
|
room_id Int?
|
||||||
Entry Entry[]
|
Entry Entry[]
|
||||||
hidden Boolean @default(false)
|
hidden Boolean @default(false)
|
||||||
|
@ -94,7 +92,6 @@ model Category {
|
||||||
color String?
|
color String?
|
||||||
description String?
|
description String?
|
||||||
EntryVersion EntryVersion[]
|
EntryVersion EntryVersion[]
|
||||||
hidden Boolean @default(false)
|
|
||||||
|
|
||||||
@@map("categories")
|
@@map("categories")
|
||||||
}
|
}
|
||||||
|
@ -123,7 +120,7 @@ model EntryVersion {
|
||||||
text String
|
text String
|
||||||
date DateTime @db.Date
|
date DateTime @db.Date
|
||||||
|
|
||||||
category Category? @relation(fields: [category_id], references: [id], onDelete: Restrict)
|
category Category? @relation(fields: [category_id], references: [id], onDelete: SetNull)
|
||||||
category_id Int?
|
category_id Int?
|
||||||
priority Boolean
|
priority Boolean
|
||||||
|
|
||||||
|
|
27
release.sh
27
release.sh
|
@ -1,27 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
CHANGELOG="CHANGELOG.md"
|
|
||||||
|
|
||||||
VERSION=$(jq -r '.version' package.json)
|
|
||||||
TAG="v${VERSION}"
|
|
||||||
echo "Releasing $TAG:"
|
|
||||||
|
|
||||||
if git rev-parse "$TAG" >/dev/null 2>&1; then echo "version tag $TAG already exists"; exit 1; fi
|
|
||||||
|
|
||||||
CLIFF_ARGS="--tag '${TAG}' --unreleased"
|
|
||||||
echo "git-cliff $CLIFF_ARGS"
|
|
||||||
if [ -f "$CHANGELOG" ]; then
|
|
||||||
eval "git-cliff $CLIFF_ARGS --prepend '$CHANGELOG'"
|
|
||||||
else
|
|
||||||
eval "git-cliff $CLIFF_ARGS --output '$CHANGELOG'"
|
|
||||||
fi
|
|
||||||
|
|
||||||
editor "$CHANGELOG"
|
|
||||||
|
|
||||||
git add "$CHANGELOG"
|
|
||||||
git commit -m "chore(release): release v$VERSION"
|
|
||||||
|
|
||||||
awk 'BEGIN{RS="(^|\n)## [^\n]+\n*"} NR==2 { print }' "$CHANGELOG" | git tag -as -F - --cleanup whitespace "$TAG"
|
|
||||||
|
|
||||||
echo "🚀 Run 'git push origin $TAG' to publish"
|
|
5
src/app.d.ts
vendored
5
src/app.d.ts
vendored
|
@ -6,7 +6,7 @@ declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
// interface Error {}
|
// interface Error {}
|
||||||
interface Locals {
|
interface Locals {
|
||||||
session: Session | null;
|
session: Session;
|
||||||
}
|
}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
|
@ -18,9 +18,6 @@ declare global {
|
||||||
"on:outclick"?: CompositionEventHandler<T>;
|
"on:outclick"?: CompositionEventHandler<T>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare const __VERSION__: string;
|
|
||||||
declare const __LASTMOD__: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
26
src/app.pcss
26
src/app.pcss
|
@ -15,32 +15,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
|
||||||
--toastBackground: oklch(var(--b3));
|
|
||||||
--toastColor: oklch(var(--bc));
|
|
||||||
--toastLeftBorder: oklch(var(--p));
|
|
||||||
--toastBarBackground: oklch(var(--bc) / 0.4);
|
|
||||||
--toastContainerTop: 4rem;
|
|
||||||
--toastContainerRight: 1rem;
|
|
||||||
--toastBarHeight: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-error {
|
|
||||||
--toastLeftBorder: oklch(var(--er));
|
|
||||||
}
|
|
||||||
|
|
||||||
._toastItem {
|
|
||||||
border-left: solid 6px var(--toastLeftBorder) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
._toastMsg {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
text-align: initial;
|
text-align: initial;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import type { ActionReturn } from "svelte/action";
|
export default function clickOutside(node: Element) {
|
||||||
|
const handleClick = (event: MouseEvent) => {
|
||||||
export default function clickOutside(node: Element): ActionReturn {
|
|
||||||
const handleClick = (event: MouseEvent): void => {
|
|
||||||
const tnode = event.target as Element;
|
const tnode = event.target as Element;
|
||||||
|
|
||||||
if (!node.contains(tnode)) {
|
if (!node.contains(tnode)) {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { browser } from "$app/environment";
|
import { browser } from "$app/environment";
|
||||||
|
|
||||||
import { mdiClose } from "@mdi/js";
|
import { mdiClose } from "@mdi/js";
|
||||||
|
@ -7,8 +6,6 @@
|
||||||
import { createFloatingActions } from "svelte-floating-ui";
|
import { createFloatingActions } from "svelte-floating-ui";
|
||||||
import { shift } from "svelte-floating-ui/dom";
|
import { shift } from "svelte-floating-ui/dom";
|
||||||
|
|
||||||
import { toastError } from "$lib/shared/util/toast";
|
|
||||||
|
|
||||||
import Icon from "$lib/components/ui/Icon.svelte";
|
import Icon from "$lib/components/ui/Icon.svelte";
|
||||||
import IconButton from "$lib/components/ui/IconButton.svelte";
|
import IconButton from "$lib/components/ui/IconButton.svelte";
|
||||||
import outclick from "$lib/actions/outclick";
|
import outclick from "$lib/actions/outclick";
|
||||||
|
@ -32,7 +29,7 @@
|
||||||
/** Set of item IDs that should be hidden from the list */
|
/** Set of item IDs that should be hidden from the list */
|
||||||
export let hiddenIds: Set<string | number> = new Set();
|
export let hiddenIds: Set<string | number> = new Set();
|
||||||
/** Object to cache fetched items in */
|
/** Object to cache fetched items in */
|
||||||
export let cache: Record<string, T[]> = {};
|
export let cache: { [key: string]: T[] } = {};
|
||||||
/** Key in cache object under which fetched items are stored */
|
/** Key in cache object under which fetched items are stored */
|
||||||
export let cacheKey: string | undefined = undefined;
|
export let cacheKey: string | undefined = undefined;
|
||||||
/** Input field placeholder */
|
/** Input field placeholder */
|
||||||
|
@ -59,9 +56,9 @@
|
||||||
|
|
||||||
/** Selection callback. Returns the new input value after selection */
|
/** Selection callback. Returns the new input value after selection */
|
||||||
export let onSelect: (item: T, kb: boolean) => OnSelectResult | void = () => {};
|
export let onSelect: (item: T, kb: boolean) => OnSelectResult | void = () => {};
|
||||||
export let onUnselect = (): void => {};
|
export let onUnselect = () => {};
|
||||||
export let onClose = (kb: boolean): void => {};
|
export let onClose = (kb: boolean) => {};
|
||||||
export let onBackspace = (): void => {};
|
export let onBackspace = () => {};
|
||||||
|
|
||||||
let opened = false;
|
let opened = false;
|
||||||
let highlightIndex = 0;
|
let highlightIndex = 0;
|
||||||
|
@ -81,7 +78,7 @@
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function setInputValue(v: string): void {
|
function setInputValue(v: string) {
|
||||||
if (inputElm) inputElm.value = v;
|
if (inputElm) inputElm.value = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,14 +98,14 @@
|
||||||
if (cacheKey) cache[cacheKey] = fetchedItems;
|
if (cacheKey) cache[cacheKey] = fetchedItems;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
updateSearch();
|
updateSearch();
|
||||||
}).catch((e) => toastError("Konnte Items nicht laden:\n" + e));
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function markSelection(): void {
|
function markSelection() {
|
||||||
if (selection) {
|
if (selection) {
|
||||||
if (selection.id) {
|
if (selection.id) {
|
||||||
const i = srcItems.findIndex((itm) => itm.id === selection?.id);
|
const i = srcItems.findIndex((itm) => itm.id === selection?.id);
|
||||||
|
@ -126,7 +123,7 @@
|
||||||
highlight();
|
highlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSelection(): void {
|
function clearSelection() {
|
||||||
selection = null;
|
selection = null;
|
||||||
onUnselect();
|
onUnselect();
|
||||||
setInputValue("");
|
setInputValue("");
|
||||||
|
@ -134,14 +131,14 @@
|
||||||
updateSearch();
|
updateSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onInput(): void {
|
function onInput() {
|
||||||
selection = null;
|
selection = null;
|
||||||
onUnselect();
|
onUnselect();
|
||||||
opened = true;
|
opened = true;
|
||||||
updateSearch();
|
updateSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSearch(): void {
|
function updateSearch() {
|
||||||
if (loadSrcItems()) {
|
if (loadSrcItems()) {
|
||||||
const searchWord = inputValue().toLowerCase().trim();
|
const searchWord = inputValue().toLowerCase().trim();
|
||||||
filteredItems = !selection && searchWord.length > 0
|
filteredItems = !selection && searchWord.length > 0
|
||||||
|
@ -155,7 +152,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function open(): void {
|
export function open() {
|
||||||
if (!opened) {
|
if (!opened) {
|
||||||
updateSearch();
|
updateSearch();
|
||||||
}
|
}
|
||||||
|
@ -163,14 +160,14 @@
|
||||||
if (inputElm) inputElm.focus();
|
if (inputElm) inputElm.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function close(kb = true): void {
|
export function close(kb = true) {
|
||||||
if (opened) {
|
if (opened) {
|
||||||
onClose(kb);
|
onClose(kb);
|
||||||
}
|
}
|
||||||
opened = false;
|
opened = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectListItem(item: T | undefined, kb: boolean): void {
|
function selectListItem(item: T | undefined, kb: boolean) {
|
||||||
if (item) {
|
if (item) {
|
||||||
selection = item;
|
selection = item;
|
||||||
const selRes = onSelect(item, kb);
|
const selRes = onSelect(item, kb);
|
||||||
|
@ -185,10 +182,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(e: KeyboardEvent): void {
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
let { key } = e;
|
let { key } = e;
|
||||||
if (key === "Tab" && e.shiftKey) key = "ShiftTab";
|
if (key === "Tab" && e.shiftKey) key = "ShiftTab";
|
||||||
const fnmap: Record<string, () => void> = {
|
const fnmap: { [key: string]: () => void } = {
|
||||||
Tab: () => close,
|
Tab: () => close,
|
||||||
ShiftTab: () => close,
|
ShiftTab: () => close,
|
||||||
ArrowDown: () => {
|
ArrowDown: () => {
|
||||||
|
@ -226,7 +223,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyPress(e: KeyboardEvent): void {
|
function onKeyPress(e: KeyboardEvent) {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
if (opened) {
|
if (opened) {
|
||||||
selectItem();
|
selectItem();
|
||||||
|
@ -235,7 +232,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBlur(): void {
|
function onBlur() {
|
||||||
if (!selection) {
|
if (!selection) {
|
||||||
if (!noAutoselect1 && filteredItems.length === 1) {
|
if (!noAutoselect1 && filteredItems.length === 1) {
|
||||||
selectListItem(filteredItems[0], true);
|
selectListItem(filteredItems[0], true);
|
||||||
|
@ -245,7 +242,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function highlight(): void {
|
function highlight() {
|
||||||
if (browser && opened) {
|
if (browser && opened) {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
const query = ".selected";
|
const query = ".selected";
|
||||||
|
@ -264,7 +261,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectItem(): void {
|
function selectItem() {
|
||||||
const listItem = filteredItems[highlightIndex];
|
const listItem = filteredItems[highlightIndex];
|
||||||
selectListItem(listItem, true);
|
selectListItem(listItem, true);
|
||||||
}
|
}
|
||||||
|
@ -351,7 +348,7 @@
|
||||||
top: 0px;
|
top: 0px;
|
||||||
max-height: calc(15 * (1rem + 10px) + 15px);
|
max-height: calc(15 * (1rem + 10px) + 15px);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@apply bg-base-100 text-base-content rounded-btn p-2 border border-base-content/30;
|
@apply bg-neutral text-neutral-content rounded-btn p-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-list:empty {
|
.autocomplete-list:empty {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import { InputType, isFilterValueless } from "./types";
|
import { InputType, isFilterValueless } from "./types";
|
||||||
|
|
||||||
/** Filter definitions */
|
/** Filter definitions */
|
||||||
export let FILTERS: Record<string, FilterDef>;
|
export let FILTERS: { [key: string]: FilterDef };
|
||||||
/** Filter data from the query */
|
/** Filter data from the query */
|
||||||
export let filterData: FilterQdata | null | undefined;
|
export let filterData: FilterQdata | null | undefined;
|
||||||
/** Callback when filters are updated */
|
/** Callback when filters are updated */
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
let autocomplete: Autocomplete<BaseItem> | undefined;
|
let autocomplete: Autocomplete<BaseItem> | undefined;
|
||||||
let activeFilters: FilterData[] = [];
|
let activeFilters: FilterData[] = [];
|
||||||
const cache: Record<string, BaseItem[]> = {};
|
const cache: { [key: string]: BaseItem[] } = {};
|
||||||
let searchVal = "";
|
let searchVal = "";
|
||||||
const searchDebounce = new Debouncer(400, () => {
|
const searchDebounce = new Debouncer(400, () => {
|
||||||
onUpdate(getFilterQdata());
|
onUpdate(getFilterQdata());
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
updateFromQueryData(filterData ?? {});
|
updateFromQueryData(filterData ?? {});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFromQueryData(fd: FilterQdata): void {
|
function updateFromQueryData(fd: FilterQdata) {
|
||||||
const filters: FilterData[] = [];
|
const filters: FilterData[] = [];
|
||||||
for (const [id, value] of Object.entries(fd)) {
|
for (const [id, value] of Object.entries(fd)) {
|
||||||
// If filter is hidden or undefined, dont display it
|
// If filter is hidden or undefined, dont display it
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
return new Set();
|
return new Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusInput(): void {
|
function focusInput() {
|
||||||
if (autocomplete) autocomplete.open();
|
if (autocomplete) autocomplete.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +166,7 @@
|
||||||
return !valueless;
|
return !valueless;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFilter(i: number): void {
|
function removeFilter(i: number) {
|
||||||
const shouldUpdate = isFilterValueless(FILTERS[activeFilters[i].id].inputType)
|
const shouldUpdate = isFilterValueless(FILTERS[activeFilters[i].id].inputType)
|
||||||
|| activeFilters[i].selection !== null;
|
|| activeFilters[i].selection !== null;
|
||||||
activeFilters.splice(i, 1);
|
activeFilters.splice(i, 1);
|
||||||
|
@ -174,15 +174,15 @@
|
||||||
if (shouldUpdate) updateFilter();
|
if (shouldUpdate) updateFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFilter(): void {
|
function updateFilter() {
|
||||||
onUpdate(getFilterQdata());
|
onUpdate(getFilterQdata());
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSearchInput(e: Event): void {
|
function onSearchInput(e: Event) {
|
||||||
searchDebounce.trigger();
|
searchDebounce.trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSearchKeypress(e: KeyboardEvent): void {
|
function onSearchKeypress(e: KeyboardEvent) {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
searchDebounce.now();
|
searchDebounce.now();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,24 +11,24 @@
|
||||||
|
|
||||||
export let filter: FilterDef;
|
export let filter: FilterDef;
|
||||||
export let hiddenIds: () => Set<string | number> = () => new Set();
|
export let hiddenIds: () => Set<string | number> = () => new Set();
|
||||||
export let cache: Record<string, BaseItem[]> = {};
|
export let cache: { [key: string]: BaseItem[] } = {};
|
||||||
export let fdata: FilterData;
|
export let fdata: FilterData;
|
||||||
|
|
||||||
export let onRemove = (): void => {};
|
export let onRemove = () => {};
|
||||||
export let onSelection = (selection: SelectionOrText, kb: boolean): void => {};
|
export let onSelection = (selection: SelectionOrText, kb: boolean) => {};
|
||||||
|
|
||||||
let autocomplete: Autocomplete<BaseItem> | undefined;
|
let autocomplete: Autocomplete<BaseItem> | undefined;
|
||||||
|
|
||||||
function startEditing(): void {
|
function startEditing() {
|
||||||
fdata.editing = true;
|
fdata.editing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopEditing(kb = false): void {
|
function stopEditing(kb = false) {
|
||||||
fdata.editing = false;
|
fdata.editing = false;
|
||||||
if (fdata.selection) onSelection(fdata.selection, kb);
|
if (fdata.selection) onSelection(fdata.selection, kb);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClose(kb = false): void {
|
function onClose(kb = false) {
|
||||||
if (fdata.selection) stopEditing(kb);
|
if (fdata.selection) stopEditing(kb);
|
||||||
else onRemove();
|
else onRemove();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,15 @@
|
||||||
import Icon from "$lib/components/ui/Icon.svelte";
|
import Icon from "$lib/components/ui/Icon.svelte";
|
||||||
|
|
||||||
export let href = "";
|
export let href = "";
|
||||||
export let onSave = (): void => {};
|
export let onSave = () => {};
|
||||||
export let onRemove = (): void => {};
|
export let onRemove = () => {};
|
||||||
|
|
||||||
function onSaveInt(e: MouseEvent): void {
|
function onSaveInt(e: MouseEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onSave();
|
onSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRemoveInt(e: MouseEvent): void {
|
function onRemoveInt(e: MouseEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onRemove();
|
onRemove();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,79 +1,58 @@
|
||||||
<!-- Bar of saved filter chips -->
|
<!-- Bar of saved filter chips -->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { mdiPlus } from "@mdi/js";
|
import { mdiPlus } from "@mdi/js";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
import type { SavedFilter } from "$lib/shared/model";
|
import type { SavedFilter } from "$lib/shared/model";
|
||||||
import { trpc } from "$lib/shared/trpc";
|
import { trpc } from "$lib/shared/trpc";
|
||||||
import { isDefaultFilter } from "$lib/shared/util";
|
|
||||||
import { toastError, toastInfo } from "$lib/shared/util/toast";
|
|
||||||
|
|
||||||
import Icon from "$lib/components/ui/Icon.svelte";
|
import Icon from "$lib/components/ui/Icon.svelte";
|
||||||
import { savedFilters } from "$lib/stores";
|
import LoadingIcon from "$lib/components/ui/LoadingIcon.svelte";
|
||||||
|
|
||||||
import Chip from "./SavedFilterChip.svelte";
|
import Chip from "./SavedFilterChip.svelte";
|
||||||
|
|
||||||
export let view: string;
|
export let view: string;
|
||||||
|
|
||||||
$: filters = $savedFilters[view] ?? [];
|
let filters: SavedFilter[] | null = null;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
trpc().savedFilter.get.query(view).then((res) => {
|
||||||
|
filters = res;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function getQuery(): string {
|
function getQuery(): string {
|
||||||
return window.location.search.substring(1);
|
return window.location.search.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSavedFilter(filter: SavedFilter): void {
|
function create() {
|
||||||
savedFilters.update((v) => {
|
|
||||||
if (!v[view]) v[view] = [];
|
|
||||||
const ix = v[view].findIndex((f) => f.id === filter.id);
|
|
||||||
if (ix === -1) {
|
|
||||||
if (isDefaultFilter(filter.name)) v[view].unshift(filter);
|
|
||||||
else v[view].push(filter);
|
|
||||||
} else {
|
|
||||||
v[view][ix] = filter;
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function create(): void {
|
|
||||||
const query = getQuery();
|
const query = getQuery();
|
||||||
if (query.length === 0) {
|
if (query.length === 0) return;
|
||||||
toastInfo("Filter leer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = prompt("Name");
|
const name = prompt("Name");
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
|
|
||||||
trpc().savedFilter.create.mutate({ name, query, view }).then((id) => {
|
trpc().savedFilter.create.mutate({ name, query, view }).then((id) => {
|
||||||
toastInfo("Filter erstellt");
|
filters?.push({ id, name, query });
|
||||||
updateSavedFilter({ id, name, query });
|
filters = filters; // force reactive update
|
||||||
}).catch(toastError);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function update(ix: number): void {
|
function update(ix: number) {
|
||||||
const f = filters[ix];
|
const f = filters![ix];
|
||||||
const query = getQuery();
|
const query = getQuery();
|
||||||
if (query.length === 0) {
|
if (query.length === 0) return;
|
||||||
toastInfo("Filter leer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
trpc().savedFilter.update.mutate({ id: f.id, query }).then(() => {
|
trpc().savedFilter.update.mutate({ id: f.id, query });
|
||||||
f.query = query;
|
f.query = query;
|
||||||
toastInfo("Filter aktualisiert");
|
filters = filters; // force reactive update
|
||||||
updateSavedFilter(f);
|
|
||||||
}).catch(toastError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove(ix: number): void {
|
function remove(ix: number) {
|
||||||
const f = filters[ix];
|
const f = filters![ix];
|
||||||
trpc().savedFilter.delete.mutate(f.id).then(() => {
|
trpc().savedFilter.delete.mutate(f.id);
|
||||||
toastInfo("Filter gelöscht");
|
filters!.splice(ix, 1);
|
||||||
savedFilters.update((v) => {
|
filters = filters;
|
||||||
v[view].splice(ix, 1);
|
|
||||||
return v;
|
|
||||||
});
|
|
||||||
}).catch(toastError);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -82,15 +61,19 @@
|
||||||
Gespeicherte Filter:
|
Gespeicherte Filter:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#each filters as filter, i (filter.id)}
|
{#if filters}
|
||||||
<Chip
|
{#each filters as filter, i (filter.id)}
|
||||||
href={"?" + filter.query}
|
<Chip
|
||||||
onRemove={() => remove(i)}
|
href={"?" + filter.query}
|
||||||
onSave={() => update(i)}
|
onRemove={() => remove(i)}
|
||||||
>
|
onSave={() => update(i)}
|
||||||
{filter.name}
|
>
|
||||||
</Chip>
|
{filter.name}
|
||||||
{/each}
|
</Chip>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<LoadingIcon />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<button class="btn btn-sm btn-primary pl-1" on:click={create}>
|
<button class="btn btn-sm btn-primary pl-1" on:click={create}>
|
||||||
<Icon path={mdiPlus} />
|
<Icon path={mdiPlus} />
|
||||||
|
|
|
@ -16,28 +16,26 @@ import {
|
||||||
|
|
||||||
import { WEEK_LIMIT } from "$lib/shared/constants";
|
import { WEEK_LIMIT } from "$lib/shared/constants";
|
||||||
import { trpc } from "$lib/shared/trpc";
|
import { trpc } from "$lib/shared/trpc";
|
||||||
import { DateRange } from "$lib/shared/util";
|
import { DateRange, dateToYMD } from "$lib/shared/util";
|
||||||
|
|
||||||
import { type FilterDef, InputType, type BaseItem } from "./types";
|
import { type FilterDef, InputType, type BaseItem } from "./types";
|
||||||
|
|
||||||
export function weekFilterItems(): BaseItem[] {
|
export function weekFilterItems(earlierLater: boolean): BaseItem[] {
|
||||||
const range = DateRange.thisWeek();
|
const range = DateRange.thisWeek();
|
||||||
|
|
||||||
const res: BaseItem[] = [];
|
const res = [];
|
||||||
const addRange = (r: DateRange): void => {
|
if (earlierLater) res.push({ id: ".." + dateToYMD(range.start!), name: "Früher" });
|
||||||
res.push({ id: r.toString(), name: r.format() });
|
|
||||||
};
|
|
||||||
|
|
||||||
addRange(new DateRange(null, range.start));
|
|
||||||
for (let i = 0; i < WEEK_LIMIT; i++) {
|
for (let i = 0; i < WEEK_LIMIT; i++) {
|
||||||
addRange(range);
|
res.push({ id: range.toString(), name: range.format() });
|
||||||
range.addDays(7);
|
range.addDays(7);
|
||||||
}
|
}
|
||||||
addRange(new DateRange(range.start, null));
|
|
||||||
|
if (earlierLater) res.push({ id: dateToYMD(range.start!) + "..", name: "Später" });
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ENTRY_FILTERS: Record<string, FilterDef> = {
|
export const ENTRY_FILTERS: { [key: string]: FilterDef } = {
|
||||||
category: {
|
category: {
|
||||||
id: "category",
|
id: "category",
|
||||||
name: "Kategorie",
|
name: "Kategorie",
|
||||||
|
@ -107,11 +105,11 @@ export const ENTRY_FILTERS: Record<string, FilterDef> = {
|
||||||
name: "Woche",
|
name: "Woche",
|
||||||
icon: mdiCalendar,
|
icon: mdiCalendar,
|
||||||
inputType: InputType.FilterList,
|
inputType: InputType.FilterList,
|
||||||
options: async () => weekFilterItems(),
|
options: async () => weekFilterItems(true),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PATIENT_FILTER: Record<string, FilterDef> = {
|
export const PATIENT_FILTER: { [key: string]: FilterDef } = {
|
||||||
station: {
|
station: {
|
||||||
id: "station",
|
id: "station",
|
||||||
name: "Station",
|
name: "Station",
|
||||||
|
|
|
@ -36,10 +36,9 @@ export type FilterData = {
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FilterQdata = Record<
|
export type FilterQdata = {
|
||||||
string,
|
[key: string]: string | number | boolean | { id: string | number; name?: string }[];
|
||||||
string | number | boolean | { id: string | number; name?: string }[]
|
};
|
||||||
>;
|
|
||||||
|
|
||||||
export function isFilterValueless(inputType: InputType): boolean {
|
export function isFilterValueless(inputType: InputType): boolean {
|
||||||
return inputType === InputType.None || inputType === InputType.Boolean;
|
return inputType === InputType.None || inputType === InputType.Boolean;
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
import type { Category } from "$lib/shared/model";
|
import type { Category } from "$lib/shared/model";
|
||||||
import { ZCategoryNew } from "$lib/shared/model/validation";
|
import { ZCategoryNew } from "$lib/shared/model/validation";
|
||||||
import { superformConfig } from "$lib/shared/util";
|
|
||||||
|
|
||||||
import FormField from "$lib/components/ui/FormField.svelte";
|
import FormField from "$lib/components/ui/FormField.svelte";
|
||||||
import Header from "$lib/components/ui/Header.svelte";
|
import Header from "$lib/components/ui/Header.svelte";
|
||||||
|
@ -22,7 +21,6 @@
|
||||||
} = superForm(formData, {
|
} = superForm(formData, {
|
||||||
validators: schema,
|
validators: schema,
|
||||||
resetForm: category === null,
|
resetForm: category === null,
|
||||||
...superformConfig("Kategorie"),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
export let hidden: boolean;
|
|
||||||
export let hasEntries: boolean;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if hidden}
|
|
||||||
<button
|
|
||||||
name="hide"
|
|
||||||
class="btn btn-primary"
|
|
||||||
type="submit"
|
|
||||||
value="0"
|
|
||||||
>Einblenden</button
|
|
||||||
>
|
|
||||||
{:else if hasEntries}
|
|
||||||
<button name="hide" class="btn" type="submit" value="1">Ausblenden</button>
|
|
||||||
{:else}
|
|
||||||
<button name="delete" class="btn btn-error" type="submit" value="1">Löschen</button>
|
|
||||||
{/if}
|
|
|
@ -7,7 +7,6 @@
|
||||||
import { ZPatientNew } from "$lib/shared/model/validation";
|
import { ZPatientNew } from "$lib/shared/model/validation";
|
||||||
import { trpc } from "$lib/shared/trpc";
|
import { trpc } from "$lib/shared/trpc";
|
||||||
import type { RouterOutput } from "$lib/shared/trpc";
|
import type { RouterOutput } from "$lib/shared/trpc";
|
||||||
import { superformConfig } from "$lib/shared/util";
|
|
||||||
|
|
||||||
import Autocomplete from "$lib/components/filter/Autocomplete.svelte";
|
import Autocomplete from "$lib/components/filter/Autocomplete.svelte";
|
||||||
import FormField from "$lib/components/ui/FormField.svelte";
|
import FormField from "$lib/components/ui/FormField.svelte";
|
||||||
|
@ -24,7 +23,6 @@
|
||||||
} = superForm(formData, {
|
} = superForm(formData, {
|
||||||
validators: schema,
|
validators: schema,
|
||||||
resetForm: patient === null,
|
resetForm: patient === null,
|
||||||
...superformConfig("Patient"),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
import { ZRoomNew } from "$lib/shared/model/validation";
|
import { ZRoomNew } from "$lib/shared/model/validation";
|
||||||
import { trpc, type RouterOutput } from "$lib/shared/trpc";
|
import { trpc, type RouterOutput } from "$lib/shared/trpc";
|
||||||
import { superformConfig } from "$lib/shared/util";
|
|
||||||
|
|
||||||
import Autocomplete from "$lib/components/filter/Autocomplete.svelte";
|
import Autocomplete from "$lib/components/filter/Autocomplete.svelte";
|
||||||
import FormField from "$lib/components/ui/FormField.svelte";
|
import FormField from "$lib/components/ui/FormField.svelte";
|
||||||
|
@ -23,7 +22,6 @@
|
||||||
} = superForm(formData, {
|
} = superForm(formData, {
|
||||||
validators: schema,
|
validators: schema,
|
||||||
resetForm: room === null,
|
resetForm: room === null,
|
||||||
...superformConfig("Zimmer"),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
import type { Station } from "$lib/shared/model";
|
import type { Station } from "$lib/shared/model";
|
||||||
import { ZStationNew } from "$lib/shared/model/validation";
|
import { ZStationNew } from "$lib/shared/model/validation";
|
||||||
import { superformConfig } from "$lib/shared/util";
|
|
||||||
|
|
||||||
import FormField from "$lib/components/ui/FormField.svelte";
|
import FormField from "$lib/components/ui/FormField.svelte";
|
||||||
import Header from "$lib/components/ui/Header.svelte";
|
import Header from "$lib/components/ui/Header.svelte";
|
||||||
|
@ -22,7 +21,6 @@
|
||||||
} = superForm(formData, {
|
} = superForm(formData, {
|
||||||
validators: schema,
|
validators: schema,
|
||||||
resetForm: station === null,
|
resetForm: station === null,
|
||||||
...superformConfig("Station"),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
? colorToHex(getTextColor(hexToColor(category.color)))
|
? colorToHex(getTextColor(hexToColor(category.color)))
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
function onClick(e: MouseEvent): void {
|
function onClick(e: MouseEvent) {
|
||||||
gotoEntityQuery(
|
gotoEntityQuery(
|
||||||
{
|
{
|
||||||
filter: {
|
filter: {
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
export let patientId: number | null = null;
|
export let patientId: number | null = null;
|
||||||
export let view: string | undefined = undefined;
|
export let view: string | undefined = undefined;
|
||||||
|
|
||||||
function paginationUpdate(pagination: PaginationRequest): void {
|
function paginationUpdate(pagination: PaginationRequest) {
|
||||||
updateQuery({
|
updateQuery({
|
||||||
filter: query.filter,
|
filter: query.filter,
|
||||||
pagination,
|
pagination,
|
||||||
|
@ -30,11 +30,11 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterUpdate(filter: FilterQdata | undefined): void {
|
function filterUpdate(filter: FilterQdata | undefined) {
|
||||||
updateQuery({ filter, sort: query.sort });
|
updateQuery({ filter, sort: query.sort });
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortUpdate(sort: SortRequest | undefined): void {
|
function sortUpdate(sort: SortRequest | undefined) {
|
||||||
updateQuery({
|
updateQuery({
|
||||||
filter: query.filter,
|
filter: query.filter,
|
||||||
pagination: query.pagination,
|
pagination: query.pagination,
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateQuery(q: typeof query): void {
|
function updateQuery(q: typeof query) {
|
||||||
if (browser) {
|
if (browser) {
|
||||||
if (patientId !== null && q.filter?.patient) delete q.filter.patient;
|
if (patientId !== null && q.filter?.patient) delete q.filter.patient;
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
export let patients: RouterOutput["patient"]["list"];
|
export let patients: RouterOutput["patient"]["list"];
|
||||||
export let baseUrl: string;
|
export let baseUrl: string;
|
||||||
|
|
||||||
function paginationUpdate(pagination: PaginationRequest): void {
|
function paginationUpdate(pagination: PaginationRequest) {
|
||||||
updateQuery({
|
updateQuery({
|
||||||
filter: query.filter,
|
filter: query.filter,
|
||||||
pagination,
|
pagination,
|
||||||
|
@ -28,11 +28,11 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterUpdate(filter: FilterQdata | undefined): void {
|
function filterUpdate(filter: FilterQdata | undefined) {
|
||||||
updateQuery({ filter, sort: query.sort });
|
updateQuery({ filter, sort: query.sort });
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortUpdate(sort: SortRequest | undefined): void {
|
function sortUpdate(sort: SortRequest | undefined) {
|
||||||
updateQuery({
|
updateQuery({
|
||||||
filter: query.filter,
|
filter: query.filter,
|
||||||
pagination: query.pagination,
|
pagination: query.pagination,
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateQuery(q: typeof query): void {
|
function updateQuery(q: typeof query) {
|
||||||
if (browser) {
|
if (browser) {
|
||||||
// Update page URL
|
// Update page URL
|
||||||
const url = getQueryUrl(q, baseUrl);
|
const url = getQueryUrl(q, baseUrl);
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
export let patient: RouterOutput["patient"]["list"]["items"][0];
|
export let patient: RouterOutput["patient"]["list"]["items"][0];
|
||||||
export let baseUrl: string;
|
export let baseUrl: string;
|
||||||
|
|
||||||
function onClick(e: MouseEvent): void {
|
function onClick(e: MouseEvent) {
|
||||||
gotoEntityQuery(
|
gotoEntityQuery(
|
||||||
{
|
{
|
||||||
filter: {
|
filter: {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { mdiFilter } from "@mdi/js";
|
import { mdiClose, 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 { SortRequest } from "$lib/shared/model";
|
||||||
|
@ -63,6 +63,9 @@
|
||||||
>
|
>
|
||||||
<Icon path={mdiFilter} size={1.2} />
|
<Icon path={mdiFilter} size={1.2} />
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-circle btn-ghost btn-xs inline">
|
||||||
|
<Icon path={mdiClose} size={1.2} />
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
export let room: Room;
|
export let room: Room;
|
||||||
export let baseUrl = URL_ENTRIES;
|
export let baseUrl = URL_ENTRIES;
|
||||||
|
|
||||||
function onClick(e: MouseEvent): void {
|
function onClick(e: MouseEvent) {
|
||||||
gotoEntityQuery(
|
gotoEntityQuery(
|
||||||
{
|
{
|
||||||
filter: {
|
filter: {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
sorting = 0;
|
sorting = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClick(): void {
|
function onClick() {
|
||||||
if (sorting === 2) {
|
if (sorting === 2) {
|
||||||
sortUpdate(undefined);
|
sortUpdate(undefined);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
export let baseUrl = URL_ENTRIES;
|
export let baseUrl = URL_ENTRIES;
|
||||||
export let filterName: string = "author";
|
export let filterName: string = "author";
|
||||||
|
|
||||||
function onClick(e: MouseEvent): void {
|
function onClick(e: MouseEvent) {
|
||||||
const query: EntityQuery = { filter: {} };
|
const query: EntityQuery = { filter: {} };
|
||||||
// @ts-expect-error filterName is checked
|
// @ts-expect-error filterName is checked
|
||||||
query.filter[filterName] = [{ id: user.id, name: user.name ?? "" }];
|
query.filter[filterName] = [{ id: user.id, name: user.name ?? "" }];
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { mdiEye, mdiEyeOff } from "@mdi/js";
|
|
||||||
|
|
||||||
import Icon from "./Icon.svelte";
|
|
||||||
|
|
||||||
export let hidden: boolean;
|
|
||||||
export let baseUrl: string;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<a class="btn btn-sm" href={baseUrl + (hidden ? "" : "?hidden")}>
|
|
||||||
<Icon path={hidden ? mdiEye : mdiEyeOff} />
|
|
||||||
</a>
|
|
|
@ -7,7 +7,7 @@
|
||||||
let showProgress = false;
|
let showProgress = false;
|
||||||
let showError = false;
|
let showError = false;
|
||||||
|
|
||||||
export function start(): void {
|
export function start() {
|
||||||
navprogress = 5;
|
navprogress = 5;
|
||||||
showProgress = true;
|
showProgress = true;
|
||||||
showError = false;
|
showError = false;
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reset(): void {
|
export function reset() {
|
||||||
clearInterval(navInterval);
|
clearInterval(navInterval);
|
||||||
navprogress = 100;
|
navprogress = 100;
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function error(): void {
|
export function error() {
|
||||||
showError = true;
|
showError = true;
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let active: boolean;
|
export let active: boolean;
|
||||||
export let href: string;
|
export let href: string;
|
||||||
export let ddn = false;
|
|
||||||
|
|
||||||
const ddnClass = "decoration-primary decoration-4 underline-offset-4";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a
|
<div>
|
||||||
class={ddn ? ddnClass : "btn btn-sm btn-ghost drawer-button font-normal " + ddnClass}
|
<a
|
||||||
class:underline={active}
|
class="btn btn-sm btn-ghost drawer-button font-normal
|
||||||
{href}><slot /></a
|
decoration-primary decoration-4 underline-offset-4"
|
||||||
>
|
class:underline={active}
|
||||||
|
{href}><slot /></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { PAGINATION_LIMIT } from "$lib/shared/constants";
|
import { PAGINATION_LIMIT } from "$lib/shared/constants";
|
||||||
import type { Pagination, PaginationRequest } from "$lib/shared/model";
|
import type { Pagination, PaginationRequest } from "$lib/shared/model";
|
||||||
|
|
||||||
import { screenWidthSmall } from "$lib/stores";
|
import { screenWidthSmall } from "$lib/stores/layout";
|
||||||
|
|
||||||
import Icon from "./Icon.svelte";
|
import Icon from "./Icon.svelte";
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
return pag;
|
return pag;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pagClick(page: number): void {
|
function pagClick(page: number) {
|
||||||
const pag = getPaginationRequest(page);
|
const pag = getPaginationRequest(page);
|
||||||
if (pag) onUpdate(pag);
|
if (pag) onUpdate(pag);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,38 +12,26 @@
|
||||||
let editing = false;
|
let editing = false;
|
||||||
let autocomplete: Autocomplete<BaseItem> | undefined;
|
let autocomplete: Autocomplete<BaseItem> | undefined;
|
||||||
|
|
||||||
export let dateRange: DateRange = new DateRange(null, DateRange.thisWeek().end);
|
export let dateRange: DateRange = DateRange.thisWeek();
|
||||||
export let onSelect: (value: DateRange) => void = () => {};
|
export let onSelect: (value: DateRange) => void = () => {};
|
||||||
|
|
||||||
function addDays(n: number): void {
|
function nextWeek() {
|
||||||
if (dateRange.start === null) {
|
dateRange.addDays(7);
|
||||||
dateRange.start = new Date(dateRange.end!);
|
|
||||||
dateRange.start.setDate(dateRange.start.getDate() - 6);
|
|
||||||
} else if (dateRange.end === null) {
|
|
||||||
dateRange.end = new Date(dateRange.start!);
|
|
||||||
dateRange.end.setDate(dateRange.end.getDate() + 6);
|
|
||||||
} else {
|
|
||||||
dateRange.addDays(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextWeek(): void {
|
|
||||||
addDays(7);
|
|
||||||
dateRange = dateRange; // update reactive
|
dateRange = dateRange; // update reactive
|
||||||
onSelect(dateRange);
|
onSelect(dateRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
function previousWeek(): void {
|
function previousWeek() {
|
||||||
addDays(-7);
|
dateRange.addDays(-7);
|
||||||
dateRange = dateRange; // update reactive
|
dateRange = dateRange; // update reactive
|
||||||
onSelect(dateRange);
|
onSelect(dateRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
function startEditing(): void {
|
function startEditing() {
|
||||||
editing = true;
|
editing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopEditing(): void {
|
function stopEditing() {
|
||||||
editing = false;
|
editing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +45,7 @@
|
||||||
{#if editing}
|
{#if editing}
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
bind:this={autocomplete}
|
bind:this={autocomplete}
|
||||||
items={async () => weekFilterItems()}
|
items={async () => weekFilterItems(false)}
|
||||||
onClose={stopEditing}
|
onClose={stopEditing}
|
||||||
onSelect={(item) => {
|
onSelect={(item) => {
|
||||||
if (typeof item.id === "string") {
|
if (typeof item.id === "string") {
|
||||||
|
|
|
@ -121,9 +121,3 @@
|
||||||
.carta-theme__default .carta-toolbar-left button.carta-active {
|
.carta-theme__default .carta-toolbar-left button.carta-active {
|
||||||
@apply font-semibold border-primary;
|
@apply font-semibold border-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.shiki, .shiki span {
|
|
||||||
color: var(--shiki-dark) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { Options } from "carta-md";
|
||||||
import { sanitizeHtml } from "$lib/shared/util";
|
import { sanitizeHtml } from "$lib/shared/util";
|
||||||
|
|
||||||
export const CARTA_CFG: Options = {
|
export const CARTA_CFG: Options = {
|
||||||
|
theme: "github-dark",
|
||||||
sanitizer: sanitizeHtml,
|
sanitizer: sanitizeHtml,
|
||||||
disableIcons: ["taskList"],
|
disableIcons: ["taskList"],
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
type AuthConfig,
|
type AuthConfig,
|
||||||
} from "@auth/core";
|
} from "@auth/core";
|
||||||
import Keycloak from "@auth/core/providers/keycloak";
|
import Keycloak from "@auth/core/providers/keycloak";
|
||||||
import type { Session } from "@auth/core/types";
|
|
||||||
import { redirect, type Handle, type RequestEvent } from "@sveltejs/kit";
|
import { redirect, type Handle, type RequestEvent } from "@sveltejs/kit";
|
||||||
import { parse } from "set-cookie-parser";
|
import { parse } from "set-cookie-parser";
|
||||||
|
|
||||||
|
@ -71,7 +70,7 @@ export async function makeAuthjsRequest(
|
||||||
event: RequestEvent,
|
event: RequestEvent,
|
||||||
authjsEndpoint: string,
|
authjsEndpoint: string,
|
||||||
params: Record<string, string>,
|
params: Record<string, string>,
|
||||||
): Promise<never> {
|
) {
|
||||||
const headers = new Headers(event.request.headers);
|
const headers = new Headers(event.request.headers);
|
||||||
headers.set("Content-Type", "application/x-www-form-urlencoded");
|
headers.set("Content-Type", "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
|
@ -88,7 +87,7 @@ export async function makeAuthjsRequest(
|
||||||
return redirect(302, res.redirect ?? "");
|
return redirect(302, res.redirect ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function auth(event: RequestEvent): Promise<Session | null> {
|
export async function auth(event: RequestEvent) {
|
||||||
const { request: req } = event;
|
const { request: req } = event;
|
||||||
setEnvDefaults(env, AUTH_CFG);
|
setEnvDefaults(env, AUTH_CFG);
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,7 @@
|
||||||
import type { Category, CategoryDetail, CategoryNew } from "$lib/shared/model";
|
import type { Category, CategoryNew } from "$lib/shared/model";
|
||||||
|
|
||||||
import { prisma } from "$lib/server/prisma";
|
import { prisma } from "$lib/server/prisma";
|
||||||
|
|
||||||
import { handleDeleteConflict } from "./util";
|
|
||||||
|
|
||||||
const SELECT = {
|
|
||||||
id: true, name: true, description: true, color: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function newCategory(category: CategoryNew): Promise<number> {
|
export async function newCategory(category: CategoryNew): Promise<number> {
|
||||||
const created = await prisma.category.create({
|
const created = await prisma.category.create({
|
||||||
data: category,
|
data: category,
|
||||||
|
@ -16,26 +10,18 @@ export async function newCategory(category: CategoryNew): Promise<number> {
|
||||||
return created.id;
|
return created.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateCategory(id: number, category: Partial<CategoryNew>): Promise<void> {
|
export async function updateCategory(id: number, category: Partial<CategoryNew>) {
|
||||||
await prisma.category.update({ where: { id }, data: category });
|
await prisma.category.update({ where: { id }, data: category });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCategory(id: number): Promise<void> {
|
export async function deleteCategory(id: number) {
|
||||||
await handleDeleteConflict(prisma.category.delete({ where: { id } }), "category with entries");
|
await prisma.category.delete({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function hideCategory(id: number, hidden: boolean): Promise<void> {
|
export async function getCategory(id: number): Promise<Category> {
|
||||||
await prisma.category.update({ where: { id }, data: { hidden } });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCategory(id: number): Promise<CategoryDetail> {
|
|
||||||
return prisma.category.findUniqueOrThrow({ where: { id } });
|
return prisma.category.findUniqueOrThrow({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCategories(hidden = false): Promise<Category[]> {
|
export async function getCategories(): Promise<Category[]> {
|
||||||
return prisma.category.findMany({ select: SELECT, where: { hidden }, orderBy: { id: "asc" } });
|
return prisma.category.findMany({ orderBy: { id: "asc" } });
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCategoryNEntries(id: number): Promise<number> {
|
|
||||||
return prisma.entry.count({ where: { EntryVersion: { some: { category_id: id } } } });
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -276,7 +276,7 @@ left join stations s on s.id = r.station_id`,
|
||||||
|
|
||||||
if (filter?.date) {
|
if (filter?.date) {
|
||||||
filterListToArray(filter.date).forEach((itm) => {
|
filterListToArray(filter.date).forEach((itm) => {
|
||||||
const dateRange = DateRange.parse(itm, true);
|
const dateRange = DateRange.parse(itm);
|
||||||
if (dateRange?.start) {
|
if (dateRange?.start) {
|
||||||
qb.addFilterClause(`ev.date >= ${qb.pvar()}`, dateRange.start);
|
qb.addFilterClause(`ev.date >= ${qb.pvar()}`, dateRange.start);
|
||||||
}
|
}
|
||||||
|
@ -286,7 +286,7 @@ left join stations s on s.id = r.station_id`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const SORT_FIELDS: Record<string, string[]> = {
|
const SORT_FIELDS: { [key: string]: string[] } = {
|
||||||
id: ["e.id"],
|
id: ["e.id"],
|
||||||
patient: ["p.last_name", "p.first_name"],
|
patient: ["p.last_name", "p.first_name"],
|
||||||
room: ["s.name", "r.name"],
|
room: ["s.name", "r.name"],
|
||||||
|
@ -405,19 +405,7 @@ export async function getNTodo(date: Date): Promise<number> {
|
||||||
order by
|
order by
|
||||||
ev2.created_at desc
|
ev2.created_at desc
|
||||||
limit 1)
|
limit 1)
|
||||||
left join entry_executions ex on
|
where ev.date <= ${date}`;
|
||||||
ex.entry_id = e.id
|
|
||||||
and ex.id = (
|
|
||||||
select
|
|
||||||
id
|
|
||||||
from
|
|
||||||
entry_executions ex2
|
|
||||||
where
|
|
||||||
ex2.entry_id = ex.entry_id
|
|
||||||
order by
|
|
||||||
ex2.created_at desc
|
|
||||||
limit 1)
|
|
||||||
where ev.date <= ${date} and ex.id is null`;
|
|
||||||
// @ts-expect-error type checked
|
// @ts-expect-error type checked
|
||||||
const count = Number(result[0].count);
|
const count = Number(result[0].count);
|
||||||
return count;
|
return count;
|
||||||
|
|
|
@ -3,4 +3,3 @@ export * from "./category";
|
||||||
export * from "./patient";
|
export * from "./patient";
|
||||||
export * from "./user";
|
export * from "./user";
|
||||||
export * from "./room";
|
export * from "./room";
|
||||||
export * from "./station";
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import type {
|
||||||
Patient,
|
Patient,
|
||||||
User,
|
User,
|
||||||
UserTag,
|
UserTag,
|
||||||
|
Room,
|
||||||
EntryVersion,
|
EntryVersion,
|
||||||
EntryExecution,
|
EntryExecution,
|
||||||
UserTagNameNonnull,
|
UserTagNameNonnull,
|
||||||
|
@ -64,6 +65,10 @@ export function mapUserTagNameNonnull(user: Omit<DbUser, "email">): UserTagNameN
|
||||||
return { id: user.id, name: user.name || "" };
|
return { id: user.id, name: user.name || "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mapRoom(room: DbRoomLn): Room {
|
||||||
|
return { id: room.id, name: room.name, station: room.station };
|
||||||
|
}
|
||||||
|
|
||||||
export function mapVersion(version: DbEntryVersionLn): EntryVersion {
|
export function mapVersion(version: DbEntryVersionLn): EntryVersion {
|
||||||
return {
|
return {
|
||||||
id: version.id,
|
id: version.id,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Patient,
|
Patient,
|
||||||
PatientNew,
|
PatientNew,
|
||||||
|
@ -7,12 +9,12 @@ import type {
|
||||||
PatientTag,
|
PatientTag,
|
||||||
SortRequest,
|
SortRequest,
|
||||||
} from "$lib/shared/model";
|
} from "$lib/shared/model";
|
||||||
import { ErrorInvalidInput } from "$lib/shared/util/error";
|
import { ErrorConflict, 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 } 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 } });
|
||||||
|
@ -20,17 +22,27 @@ export async function newPatient(patient: PatientNew): Promise<number> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update a patient */
|
/** Update a patient */
|
||||||
export async function updatePatient(id: number, patient: Partial<PatientNew>): Promise<void> {
|
export async function updatePatient(id: number, patient: Partial<PatientNew>) {
|
||||||
await prisma.patient.update({ where: { id }, data: patient });
|
await prisma.patient.update({ where: { id }, data: patient });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Delete a patient (Note: this only works if the patient is not associated with any entries) */
|
/** Delete a patient (Note: this only works if the patient is not associated with any entries) */
|
||||||
export async function deletePatient(id: number): Promise<void> {
|
export async function deletePatient(id: number) {
|
||||||
await handleDeleteConflict(prisma.patient.delete({ where: { id } }), "patient with entries");
|
try {
|
||||||
|
await prisma.patient.delete({ where: { id } });
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof PrismaClientKnownRequestError) {
|
||||||
|
// Foreign key constraint failed
|
||||||
|
if (error.code === "P2003") {
|
||||||
|
throw new ErrorConflict("cannot delete patient with entries");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Hide/show a patient */
|
/** Hide/show a patient */
|
||||||
export async function hidePatient(id: number, hidden: boolean): Promise<void> {
|
export async function hidePatient(id: number, hidden: boolean) {
|
||||||
await prisma.patient.update({ where: { id }, data: { hidden } });
|
await prisma.patient.update({ where: { id }, data: { hidden } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +109,7 @@ export async function getPatients(
|
||||||
qb.addFilterList("r.id", filter.room);
|
qb.addFilterList("r.id", filter.room);
|
||||||
qb.addFilterList("s.id", filter.station);
|
qb.addFilterList("s.id", filter.station);
|
||||||
|
|
||||||
const SORT_FIELDS: Record<string, string[]> = {
|
const SORT_FIELDS: { [key: string]: string[] } = {
|
||||||
id: ["p.id"],
|
id: ["p.id"],
|
||||||
name: ["p.last_name", "p.first_name"],
|
name: ["p.last_name", "p.first_name"],
|
||||||
first_name: ["p.first_name"],
|
first_name: ["p.first_name"],
|
||||||
|
|
|
@ -1,43 +1,61 @@
|
||||||
import type {
|
import type {
|
||||||
RoomNew, Room,
|
RoomNew, Room, Station, StationNew,
|
||||||
RoomDetail,
|
|
||||||
} from "$lib/shared/model";
|
} from "$lib/shared/model";
|
||||||
|
|
||||||
import { prisma } from "$lib/server/prisma";
|
import { prisma } from "$lib/server/prisma";
|
||||||
|
|
||||||
import { handleDeleteConflict } from "./util";
|
import { mapRoom } from "./mapping";
|
||||||
|
|
||||||
const SELECT = { id: true, name: true, station: { select: { id: true, name: true } } };
|
export async function newStation(station: StationNew): Promise<number> {
|
||||||
|
const created = await prisma.station.create({ data: station, select: { id: true } });
|
||||||
|
return created.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateStation(id: number, station: Partial<StationNew>) {
|
||||||
|
await prisma.station.update({ where: { id }, data: station });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteStation(id: number) {
|
||||||
|
await prisma.station.delete({ where: { id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStation(id: number): Promise<Station> {
|
||||||
|
return prisma.station.findUniqueOrThrow({ where: { id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStations(): Promise<Station[]> {
|
||||||
|
return prisma.station.findMany({ orderBy: { id: "asc" } });
|
||||||
|
}
|
||||||
|
|
||||||
export async function newRoom(room: RoomNew): Promise<number> {
|
export async function newRoom(room: RoomNew): Promise<number> {
|
||||||
const created = await prisma.room.create({ data: room, select: { id: true } });
|
const created = await prisma.room.create({ data: room, select: { id: true } });
|
||||||
return created.id;
|
return created.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateRoom(id: number, room: Partial<RoomNew>): Promise<void> {
|
export async function updateRoom(id: number, room: Partial<RoomNew>) {
|
||||||
await prisma.room.update({ where: { id }, data: room });
|
await prisma.room.update({ where: { id }, data: room });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRoom(id: number): Promise<void> {
|
export async function deleteRoom(id: number) {
|
||||||
await handleDeleteConflict(prisma.room.delete({ where: { id } }), "room with patients");
|
await prisma.room.delete({ where: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function hideRoom(id: number, hidden: boolean): Promise<void> {
|
export async function getRoom(id: number): Promise<Room> {
|
||||||
await prisma.room.update({ where: { id }, data: { hidden } });
|
const room = await prisma.room.findUniqueOrThrow({
|
||||||
|
where: { id },
|
||||||
|
include: { station: true },
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
id: room.id,
|
||||||
|
name: room.name,
|
||||||
|
station: room.station,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRoom(id: number): Promise<RoomDetail> {
|
export async function getRooms(): Promise<Room[]> {
|
||||||
return prisma.room.findUniqueOrThrow({ select: { ...SELECT, hidden: true }, where: { id } });
|
const rooms = await prisma.room.findMany({
|
||||||
}
|
include: { station: true },
|
||||||
|
|
||||||
export async function getRooms(hidden = false): Promise<Room[]> {
|
|
||||||
return prisma.room.findMany({
|
|
||||||
select: SELECT,
|
|
||||||
where: { hidden },
|
|
||||||
orderBy: [{ station: { name: "asc" } }, { name: "asc" }],
|
orderBy: [{ station: { name: "asc" } }, { name: "asc" }],
|
||||||
});
|
});
|
||||||
}
|
return rooms.map(mapRoom);
|
||||||
|
|
||||||
export async function getRoomNPatients(id: number): Promise<number> {
|
|
||||||
return prisma.patient.count({ where: { room_id: id } });
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
import { DEFAULT_FILTER_NAME } from "$lib/shared/constants";
|
|
||||||
import type { SavedFilter, SavedFilterNew } from "$lib/shared/model";
|
import type { SavedFilter, SavedFilterNew } from "$lib/shared/model";
|
||||||
import { isDefaultFilter } from "$lib/shared/util";
|
|
||||||
|
|
||||||
import { prisma } from "$lib/server/prisma";
|
import { prisma } from "$lib/server/prisma";
|
||||||
|
|
||||||
const SELECT = { id: true, name: true, query: true };
|
|
||||||
|
|
||||||
export async function newSavedFilter(filter: SavedFilterNew, user_id: number): Promise<number> {
|
export async function newSavedFilter(filter: SavedFilterNew, user_id: number): Promise<number> {
|
||||||
const created = await prisma.savedFilter.create({
|
const created = await prisma.savedFilter.create({
|
||||||
data: {
|
data: {
|
||||||
|
@ -16,49 +12,17 @@ export async function newSavedFilter(filter: SavedFilterNew, user_id: number): P
|
||||||
return created.id;
|
return created.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateSavedFilter(id: number, query: string, user_id: number): Promise<void> {
|
export async function updateSavedFilter(id: number, query: string, user_id: number) {
|
||||||
await prisma.savedFilter.update({ where: { id, user_id }, data: { query } });
|
await prisma.savedFilter.update({ where: { id, user_id }, data: { query } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteSavedFilter(id: number, user_id: number): Promise<void> {
|
export async function deleteSavedFilter(id: number, user_id: number) {
|
||||||
await prisma.savedFilter.delete({ where: { id, user_id } });
|
await prisma.savedFilter.delete({ where: { id, user_id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSavedFilters(user_id: number, view: string): Promise<SavedFilter[]> {
|
export async function getSavedFilters(user_id: number, view: string): Promise<SavedFilter[]> {
|
||||||
const filters = await prisma.savedFilter.findMany({
|
return prisma.savedFilter.findMany({
|
||||||
select: SELECT,
|
select: { id: true, name: true, query: true },
|
||||||
where: { user_id, view },
|
where: { user_id, view },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Move default filter to the top
|
|
||||||
const dix = filters.findIndex((f) => f.name.toLowerCase() === DEFAULT_FILTER_NAME);
|
|
||||||
if (dix !== -1) filters.unshift(filters.splice(dix, 1)[0]);
|
|
||||||
|
|
||||||
return filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getDefaultFilter(user_id: number, view: string): Promise<SavedFilter | null> {
|
|
||||||
return prisma.savedFilter.findFirst({
|
|
||||||
select: SELECT,
|
|
||||||
where: { user_id, view, name: { mode: "insensitive", equals: DEFAULT_FILTER_NAME } },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAllFilters(user_id: number): Promise<Record<string, SavedFilter[]>> {
|
|
||||||
const filters = await prisma.savedFilter.findMany({
|
|
||||||
select: {
|
|
||||||
id: true, name: true, query: true, view: true,
|
|
||||||
},
|
|
||||||
where: { user_id },
|
|
||||||
});
|
|
||||||
|
|
||||||
const grouped: Record<string, SavedFilter[]> = {};
|
|
||||||
for (const filter of filters) {
|
|
||||||
if (!grouped[filter.view]) grouped[filter.view] = [];
|
|
||||||
const f = { id: filter.id, name: filter.name, query: filter.query };
|
|
||||||
// Place default filter at the first position
|
|
||||||
if (isDefaultFilter(f.name)) grouped[filter.view].unshift(f);
|
|
||||||
else grouped[filter.view].push(f);
|
|
||||||
}
|
|
||||||
return grouped;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
import type { Station, StationDetail, StationNew } from "$lib/shared/model";
|
|
||||||
|
|
||||||
import { prisma } from "$lib/server/prisma";
|
|
||||||
|
|
||||||
import { handleDeleteConflict } from "./util";
|
|
||||||
|
|
||||||
const SELECT = { id: true, name: true };
|
|
||||||
|
|
||||||
export async function newStation(station: StationNew): Promise<number> {
|
|
||||||
const created = await prisma.station.create({ data: station, select: { id: true } });
|
|
||||||
return created.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateStation(id: number, station: Partial<StationNew>): Promise<void> {
|
|
||||||
await prisma.station.update({ where: { id }, data: station });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteStation(id: number): Promise<void> {
|
|
||||||
await handleDeleteConflict(prisma.station.delete({ where: { id } }), "station with rooms");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function hideStation(id: number, hidden: boolean): Promise<void> {
|
|
||||||
await prisma.station.update({ where: { id }, data: { hidden } });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStation(id: number): Promise<StationDetail> {
|
|
||||||
return prisma.station.findUniqueOrThrow({ where: { id } });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStations(hidden = false): Promise<Station[]> {
|
|
||||||
return prisma.station.findMany({
|
|
||||||
select: SELECT,
|
|
||||||
where: { hidden },
|
|
||||||
orderBy: { id: "asc" },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStationNRooms(id: number): Promise<number> {
|
|
||||||
return prisma.room.count({ where: { station_id: id } });
|
|
||||||
}
|
|
|
@ -1,8 +1,5 @@
|
||||||
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";
|
|
||||||
|
|
||||||
enum QueryComponentType {
|
enum QueryComponentType {
|
||||||
Normal = 1,
|
Normal = 1,
|
||||||
|
@ -23,20 +20,6 @@ export function filterListToArray<T>(fl: FilterList<T>): T[] {
|
||||||
return [fl];
|
return [fl];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleDeleteConflict(act: Promise<unknown>, msg: string): Promise<void> {
|
|
||||||
try {
|
|
||||||
await act;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof PrismaClientKnownRequestError) {
|
|
||||||
// Foreign key constraint failed
|
|
||||||
if (error.code === "P2003") {
|
|
||||||
throw new ErrorConflict(`cannot delete ${msg}; hide instead`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchQueryComponent {
|
class SearchQueryComponent {
|
||||||
word: string;
|
word: string;
|
||||||
|
|
||||||
|
@ -148,16 +131,16 @@ export class QueryBuilder {
|
||||||
this.fromClause = fromClause;
|
this.fromClause = fromClause;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPagination(pag: PaginationRequest): void {
|
setPagination(pag: PaginationRequest) {
|
||||||
if (pag.limit) this.limit = pag.limit;
|
if (pag.limit) this.limit = pag.limit;
|
||||||
if (pag.offset) this.offset = pag.offset;
|
if (pag.offset) this.offset = pag.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
addOrderClause(orderClause: string): void {
|
addOrderClause(orderClause: string) {
|
||||||
this.orderClauses.push(orderClause);
|
this.orderClauses.push(orderClause);
|
||||||
}
|
}
|
||||||
|
|
||||||
orderByFields(fields: string[], asc: boolean | undefined = undefined): void {
|
orderByFields(fields: string[], asc: boolean | undefined = undefined) {
|
||||||
const sortDir = asc === false ? " desc" : " asc";
|
const sortDir = asc === false ? " desc" : " asc";
|
||||||
const orderClause = fields.join(`${sortDir}, `) + sortDir;
|
const orderClause = fields.join(`${sortDir}, `) + sortDir;
|
||||||
this.addOrderClause(orderClause);
|
this.addOrderClause(orderClause);
|
||||||
|
@ -170,7 +153,7 @@ export class QueryBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Add a simple filter checking for equality */
|
/** Add a simple filter checking for equality */
|
||||||
addFilter(fname: string, val: unknown | undefined): void {
|
addFilter(fname: string, val: unknown | undefined) {
|
||||||
if (val === undefined) return;
|
if (val === undefined) return;
|
||||||
|
|
||||||
this.params.push(val);
|
this.params.push(val);
|
||||||
|
@ -178,13 +161,13 @@ export class QueryBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Add a SQL filter clause */
|
/** Add a SQL filter clause */
|
||||||
addFilterClause(clause: string, ...params: unknown[]): void {
|
addFilterClause(clause: string, ...params: unknown[]) {
|
||||||
this.filterClauses.push(clause);
|
this.filterClauses.push(clause);
|
||||||
this.params.push(...params);
|
this.params.push(...params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Add a list filter (value matches any item from the filter list) */
|
/** Add a list filter (value matches any item from the filter list) */
|
||||||
addFilterList(fname: string, fl: FilterList<unknown> | undefined): void {
|
addFilterList(fname: string, fl: FilterList<unknown> | undefined) {
|
||||||
if (fl === undefined) return;
|
if (fl === undefined) return;
|
||||||
|
|
||||||
this.filterClauses.push(`${fname} = any (${this.pvar()})`);
|
this.filterClauses.push(`${fname} = any (${this.pvar()})`);
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import type { RequestEvent } from "@sveltejs/kit";
|
import type { RequestEvent } from "@sveltejs/kit";
|
||||||
import { type inferAsyncReturnType, TRPCError } from "@trpc/server";
|
import { type inferAsyncReturnType, TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
import type { User } from "$lib/shared/model";
|
|
||||||
import { ZUser } from "$lib/shared/model/validation";
|
import { ZUser } from "$lib/shared/model/validation";
|
||||||
|
|
||||||
// we're not using the event parameter is this example,
|
// we're not using the event parameter is this example,
|
||||||
// hence the eslint-disable rule
|
// hence the eslint-disable rule
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
export async function createContext(event: RequestEvent): Promise<{ user: User }> {
|
export async function createContext(event: RequestEvent) {
|
||||||
if (!event.locals.session?.user) {
|
if (!event.locals.session?.user) {
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED", message: "not logged in" });
|
throw new TRPCError({ code: "UNAUTHORIZED", message: "not logged in" });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import { ZEntityId, ZCategoryNew } from "$lib/shared/model/validation";
|
||||||
ZEntityId, ZCategoryNew, ZHide, ZListHidden,
|
|
||||||
} from "$lib/shared/model/validation";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteCategory,
|
deleteCategory,
|
||||||
getCategories,
|
getCategories,
|
||||||
getCategory,
|
getCategory,
|
||||||
getCategoryNEntries,
|
|
||||||
hideCategory,
|
|
||||||
newCategory,
|
newCategory,
|
||||||
updateCategory,
|
updateCategory,
|
||||||
} from "$lib/server/query";
|
} from "$lib/server/query";
|
||||||
|
@ -17,18 +13,10 @@ import {
|
||||||
import { t, trpcWrap } from "..";
|
import { t, trpcWrap } from "..";
|
||||||
|
|
||||||
export const categoryRouter = t.router({
|
export const categoryRouter = t.router({
|
||||||
list: t.procedure.input(ZListHidden).query(async (opts) => trpcWrap(
|
list: t.procedure.query(async () => trpcWrap(getCategories)),
|
||||||
async () => getCategories(opts.input?.hidden),
|
|
||||||
)),
|
|
||||||
get: t.procedure
|
get: t.procedure
|
||||||
.input(ZEntityId)
|
.input(ZEntityId)
|
||||||
.query(async (opts) => trpcWrap(async () => {
|
.query(async (opts) => trpcWrap(async () => getCategory(opts.input))),
|
||||||
const [category, n_entries] = await Promise.all([
|
|
||||||
getCategory(opts.input),
|
|
||||||
getCategoryNEntries(opts.input),
|
|
||||||
]);
|
|
||||||
return { ...category, n_entries };
|
|
||||||
})),
|
|
||||||
create: t.procedure.input(ZCategoryNew).mutation(async (opts) => trpcWrap(async () => {
|
create: t.procedure.input(ZCategoryNew).mutation(async (opts) => trpcWrap(async () => {
|
||||||
const id = await newCategory(opts.input);
|
const id = await newCategory(opts.input);
|
||||||
return id;
|
return id;
|
||||||
|
@ -41,7 +29,4 @@ export const categoryRouter = t.router({
|
||||||
delete: t.procedure.input(ZEntityId).mutation(async (opts) => trpcWrap(async () => {
|
delete: t.procedure.input(ZEntityId).mutation(async (opts) => trpcWrap(async () => {
|
||||||
await deleteCategory(opts.input);
|
await deleteCategory(opts.input);
|
||||||
})),
|
})),
|
||||||
hide: t.procedure.input(ZHide).mutation(async (opts) => trpcWrap(async () => {
|
|
||||||
await hideCategory(opts.input.id, opts.input.hidden);
|
|
||||||
})),
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import { ZEntityId, ZPatientNew, ZPatientsQuery } from "$lib/shared/model/validation";
|
||||||
ZEntityId, ZHide, ZPatientNew, ZPatientsQuery,
|
|
||||||
} from "$lib/shared/model/validation";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deletePatient,
|
deletePatient,
|
||||||
|
@ -44,7 +42,14 @@ export const patientRouter = t.router({
|
||||||
delete: t.procedure.input(ZEntityId).mutation(async (opts) => trpcWrap(async () => {
|
delete: t.procedure.input(ZEntityId).mutation(async (opts) => trpcWrap(async () => {
|
||||||
await deletePatient(opts.input);
|
await deletePatient(opts.input);
|
||||||
})),
|
})),
|
||||||
hide: t.procedure.input(ZHide).mutation(async (opts) => trpcWrap(async () => {
|
hide: t.procedure
|
||||||
await hidePatient(opts.input.id, opts.input.hidden);
|
.input(
|
||||||
})),
|
z.object({
|
||||||
|
id: ZEntityId,
|
||||||
|
hidden: z.boolean().default(true),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async (opts) => trpcWrap(async () => {
|
||||||
|
await hidePatient(opts.input.id, opts.input.hidden);
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,28 +1,18 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import { ZEntityId, ZRoomNew } from "$lib/shared/model/validation";
|
||||||
ZEntityId, ZHide, ZListHidden, ZRoomNew,
|
|
||||||
} from "$lib/shared/model/validation";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteRoom, getRoom, getRoomNPatients, getRooms, hideRoom, newRoom, updateRoom,
|
deleteRoom, getRoom, getRooms, newRoom, updateRoom,
|
||||||
} from "$lib/server/query";
|
} from "$lib/server/query";
|
||||||
|
|
||||||
import { t, trpcWrap } from "..";
|
import { t, trpcWrap } from "..";
|
||||||
|
|
||||||
export const roomRouter = t.router({
|
export const roomRouter = t.router({
|
||||||
list: t.procedure.input(ZListHidden).query(async (opts) => trpcWrap(
|
list: t.procedure.query(async (opts) => trpcWrap(getRooms)),
|
||||||
async () => getRooms(opts.input?.hidden),
|
|
||||||
)),
|
|
||||||
get: t.procedure
|
get: t.procedure
|
||||||
.input(ZEntityId)
|
.input(ZEntityId)
|
||||||
.query(async (opts) => trpcWrap(async () => {
|
.query(async (opts) => trpcWrap(async () => getRoom(opts.input))),
|
||||||
const [room, n_patients] = await Promise.all([
|
|
||||||
getRoom(opts.input),
|
|
||||||
getRoomNPatients(opts.input),
|
|
||||||
]);
|
|
||||||
return { ...room, n_patients };
|
|
||||||
})),
|
|
||||||
create: t.procedure.input(ZRoomNew).mutation(async (opts) => trpcWrap(async () => {
|
create: t.procedure.input(ZRoomNew).mutation(async (opts) => trpcWrap(async () => {
|
||||||
const id = await newRoom(opts.input);
|
const id = await newRoom(opts.input);
|
||||||
return id;
|
return id;
|
||||||
|
@ -35,7 +25,4 @@ export const roomRouter = t.router({
|
||||||
delete: t.procedure.input(ZEntityId).mutation(async (opts) => trpcWrap(async () => {
|
delete: t.procedure.input(ZEntityId).mutation(async (opts) => trpcWrap(async () => {
|
||||||
await deleteRoom(opts.input);
|
await deleteRoom(opts.input);
|
||||||
})),
|
})),
|
||||||
hide: t.procedure.input(ZHide).mutation(async (opts) => trpcWrap(async () => {
|
|
||||||
await hideRoom(opts.input.id, opts.input.hidden);
|
|
||||||
})),
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,12 +3,7 @@ import {
|
||||||
} from "$lib/shared/model/validation";
|
} from "$lib/shared/model/validation";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteSavedFilter,
|
deleteSavedFilter, getSavedFilters, newSavedFilter, updateSavedFilter,
|
||||||
getAllFilters,
|
|
||||||
getDefaultFilter,
|
|
||||||
getSavedFilters,
|
|
||||||
newSavedFilter,
|
|
||||||
updateSavedFilter,
|
|
||||||
} from "$lib/server/query/savedFilter";
|
} from "$lib/server/query/savedFilter";
|
||||||
|
|
||||||
import { t, trpcWrap } from "..";
|
import { t, trpcWrap } from "..";
|
||||||
|
@ -17,12 +12,6 @@ export const savedFilterRouter = t.router({
|
||||||
get: t.procedure.input(fields.NameString()).query(async (opts) => trpcWrap(
|
get: t.procedure.input(fields.NameString()).query(async (opts) => trpcWrap(
|
||||||
async () => getSavedFilters(opts.ctx.user.id, opts.input),
|
async () => getSavedFilters(opts.ctx.user.id, opts.input),
|
||||||
)),
|
)),
|
||||||
getDefault: t.procedure.input(fields.NameString()).query(async (opts) => trpcWrap(
|
|
||||||
async () => getDefaultFilter(opts.ctx.user.id, opts.input),
|
|
||||||
)),
|
|
||||||
getAll: t.procedure.query(async (opts) => trpcWrap(
|
|
||||||
async () => getAllFilters(opts.ctx.user.id),
|
|
||||||
)),
|
|
||||||
create: t.procedure.input(ZSavedFilterNew).mutation(async (opts) => trpcWrap(
|
create: t.procedure.input(ZSavedFilterNew).mutation(async (opts) => trpcWrap(
|
||||||
async () => newSavedFilter(opts.input, opts.ctx.user.id),
|
async () => newSavedFilter(opts.input, opts.ctx.user.id),
|
||||||
)),
|
)),
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import { ZEntityId, ZStationNew } from "$lib/shared/model/validation";
|
||||||
ZEntityId, ZHide, ZListHidden, ZStationNew,
|
|
||||||
} from "$lib/shared/model/validation";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteStation,
|
deleteStation,
|
||||||
getStation,
|
getStation,
|
||||||
getStationNRooms,
|
|
||||||
getStations,
|
getStations,
|
||||||
hideStation,
|
|
||||||
newStation,
|
newStation,
|
||||||
updateStation,
|
updateStation,
|
||||||
} from "$lib/server/query";
|
} from "$lib/server/query";
|
||||||
|
@ -17,16 +13,10 @@ import {
|
||||||
import { t, trpcWrap } from "..";
|
import { t, trpcWrap } from "..";
|
||||||
|
|
||||||
export const stationRouter = t.router({
|
export const stationRouter = t.router({
|
||||||
list: t.procedure.input(ZListHidden).query(async (opts) => getStations(opts.input?.hidden)),
|
list: t.procedure.query(getStations),
|
||||||
get: t.procedure
|
get: t.procedure
|
||||||
.input(ZEntityId)
|
.input(ZEntityId)
|
||||||
.query(async (opts) => trpcWrap(async () => {
|
.query(async (opts) => trpcWrap(async () => getStation(opts.input))),
|
||||||
const [station, n_rooms] = await Promise.all([
|
|
||||||
getStation(opts.input),
|
|
||||||
getStationNRooms(opts.input),
|
|
||||||
]);
|
|
||||||
return { ...station, n_rooms };
|
|
||||||
})),
|
|
||||||
create: t.procedure.input(ZStationNew).mutation(async (opts) => trpcWrap(async () => {
|
create: t.procedure.input(ZStationNew).mutation(async (opts) => trpcWrap(async () => {
|
||||||
const id = await newStation(opts.input);
|
const id = await newStation(opts.input);
|
||||||
return id;
|
return id;
|
||||||
|
@ -39,7 +29,4 @@ export const stationRouter = t.router({
|
||||||
delete: t.procedure.input(ZEntityId).mutation(async (opts) => trpcWrap(async () => {
|
delete: t.procedure.input(ZEntityId).mutation(async (opts) => trpcWrap(async () => {
|
||||||
await deleteStation(opts.input);
|
await deleteStation(opts.input);
|
||||||
})),
|
})),
|
||||||
hide: t.procedure.input(ZHide).mutation(async (opts) => trpcWrap(async () => {
|
|
||||||
await hideStation(opts.input.id, opts.input.hidden);
|
|
||||||
})),
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,5 +4,3 @@ export const WEEK_LIMIT = 8;
|
||||||
export const URL_ENTRIES = "/plan";
|
export const URL_ENTRIES = "/plan";
|
||||||
export const URL_VISIT = "/visit";
|
export const URL_VISIT = "/visit";
|
||||||
export const URL_PATIENTS = "/patients";
|
export const URL_PATIENTS = "/patients";
|
||||||
|
|
||||||
export const DEFAULT_FILTER_NAME = "default";
|
|
||||||
|
|
|
@ -35,40 +35,31 @@ export type UserTagNameNonnull = {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StationDetail = {
|
export type Station = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
hidden: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Station = Omit<StationDetail, "hidden">;
|
|
||||||
|
|
||||||
export type StationNew = Omit<Station, "id">;
|
export type StationNew = Omit<Station, "id">;
|
||||||
|
|
||||||
export type RoomDetail = {
|
export type Room = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
station: Station;
|
station: Station;
|
||||||
hidden: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Room = Omit<RoomDetail, "hidden">;
|
|
||||||
|
|
||||||
export type RoomNew = {
|
export type RoomNew = {
|
||||||
name: string;
|
name: string;
|
||||||
station_id: number;
|
station_id: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CategoryDetail = {
|
export type Category = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
color: Option<string>;
|
color: Option<string>;
|
||||||
description: Option<string>;
|
description: Option<string>;
|
||||||
hidden: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Category = Omit<CategoryDetail, "hidden">;
|
|
||||||
|
|
||||||
export type CategoryNew = Omit<Category, "id">;
|
export type CategoryNew = Omit<Category, "id">;
|
||||||
|
|
||||||
export type Patient = {
|
export type Patient = {
|
||||||
|
|
|
@ -45,7 +45,7 @@ const coercedBool = z.string().toLowerCase().transform((v) => v === "true").or(z
|
||||||
|
|
||||||
function returnDataInSameOrderAsPassed<Schema extends z.ZodObject<z.ZodRawShape>>(
|
function returnDataInSameOrderAsPassed<Schema extends z.ZodObject<z.ZodRawShape>>(
|
||||||
schema: Schema,
|
schema: Schema,
|
||||||
): z.ZodEffects<z.ZodAny, z.TypeOf<Schema> | undefined> {
|
) {
|
||||||
return z.any().transform((value, ctx) => {
|
return z.any().transform((value, ctx) => {
|
||||||
const parsed = schema.safeParse(value);
|
const parsed = schema.safeParse(value);
|
||||||
if (parsed.success) {
|
if (parsed.success) {
|
||||||
|
@ -181,10 +181,3 @@ export const ZSavedFilterUpdate = z.object({
|
||||||
id: ZEntityId,
|
id: ZEntityId,
|
||||||
query: z.string(),
|
query: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZHide = z.object({
|
|
||||||
id: ZEntityId,
|
|
||||||
hidden: z.boolean().default(true),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ZListHidden = z.object({ hidden: z.boolean().optional() }).optional();
|
|
||||||
|
|
|
@ -109,6 +109,9 @@ export class DateRange {
|
||||||
/** Create a date range of the current calendar week */
|
/** Create a date range of the current calendar week */
|
||||||
static thisWeek(): DateRange {
|
static thisWeek(): DateRange {
|
||||||
const dayStart = new Date();
|
const dayStart = new Date();
|
||||||
|
// Correct for timezone
|
||||||
|
dayStart.setMinutes(dayStart.getMinutes() - dayStart.getTimezoneOffset());
|
||||||
|
|
||||||
const todayWd = dayStart.getDay();
|
const todayWd = dayStart.getDay();
|
||||||
// Day starts at Sunday (0)
|
// Day starts at Sunday (0)
|
||||||
const daysMinus = todayWd === 0 ? 6 : todayWd - 1;
|
const daysMinus = todayWd === 0 ? 6 : todayWd - 1;
|
||||||
|
@ -123,11 +126,11 @@ export class DateRange {
|
||||||
* - Range with 2 ends: `2024-04-13..2024-04-20`
|
* - Range with 2 ends: `2024-04-13..2024-04-20`
|
||||||
* - Range with 1 end: `2024-04-13..`; `..2024-04-20`
|
* - Range with 1 end: `2024-04-13..`; `..2024-04-20`
|
||||||
*/
|
*/
|
||||||
static parse(s: string, utc = false): DateRange | null {
|
static parse(s: string): DateRange | null {
|
||||||
const parts = s.split("..", 2);
|
const parts = s.split("..", 2);
|
||||||
const parsed = parts.map((p) => {
|
const parsed = parts.map((p) => {
|
||||||
if (p.length === 0) return null;
|
if (p.length === 0) return null;
|
||||||
return utc ? new Date(p) : dateFromYMD(p);
|
return dateFromYMD(p);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (parsed.length === 0
|
if (parsed.length === 0
|
||||||
|
@ -144,7 +147,7 @@ export class DateRange {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Shift the range by the given number of days. This modifies the range in-place */
|
/** Shift the range by the given number of days. This modifies the range in-place */
|
||||||
addDays(n: number): void {
|
addDays(n: number) {
|
||||||
this.start?.setDate(this.start.getDate() + n);
|
this.start?.setDate(this.start.getDate() + n);
|
||||||
this.end?.setDate(this.end.getDate() + n);
|
this.end?.setDate(this.end.getDate() + n);
|
||||||
}
|
}
|
||||||
|
@ -160,8 +163,10 @@ export class DateRange {
|
||||||
|
|
||||||
/** Return a string representation for display purposes */
|
/** Return a string representation for display purposes */
|
||||||
format(): string {
|
format(): string {
|
||||||
if (this.start === null) return "bis " + formatDate(this.end!);
|
let res = "";
|
||||||
if (this.end === null) return "ab " + formatDate(this.start);
|
if (this.start) res += formatDate(this.start);
|
||||||
return formatDate(this.start) + " \u2013 " + formatDate(this.end);
|
res += " \u2013 ";
|
||||||
|
if (this.end) res += formatDate(this.end);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { toast } from "@zerodevx/svelte-toast";
|
|
||||||
|
|
||||||
export function toastInfo(msg: string): void {
|
|
||||||
toast.push({ msg });
|
|
||||||
}
|
|
||||||
export function toastError(msg: string): void {
|
|
||||||
toast.push({ msg, classes: ["toast-error"] });
|
|
||||||
}
|
|
|
@ -4,15 +4,10 @@ import { isRedirect, error } from "@sveltejs/kit";
|
||||||
import { TRPCClientError } from "@trpc/client";
|
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 { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
|
|
||||||
import { DEFAULT_FILTER_NAME, URL_VISIT } from "$lib/shared/constants";
|
import type { EntityQuery } from "$lib/shared/model";
|
||||||
import type { EntityQuery, SavedFilter } from "$lib/shared/model";
|
import type { RouterOutput } from "$lib/shared/trpc";
|
||||||
import { type RouterOutput } from "$lib/shared/trpc";
|
|
||||||
|
|
||||||
import { DateRange } from "./date";
|
|
||||||
import { toastError, toastInfo } from "./toast";
|
|
||||||
|
|
||||||
export function formatBool(val: boolean): string {
|
export function formatBool(val: boolean): string {
|
||||||
return val ? "Ja" : "Nein";
|
return val ? "Ja" : "Nein";
|
||||||
|
@ -29,7 +24,7 @@ export function parseQueryUrl(search: string): any {
|
||||||
return qs.parse(search, { ignoreQueryPrefix: true, allowDots: true });
|
return qs.parse(search, { ignoreQueryPrefix: true, allowDots: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function gotoEntityQuery(query: EntityQuery, basePath: string): void {
|
export function gotoEntityQuery(query: EntityQuery, basePath: string) {
|
||||||
if (window && window.location.pathname.startsWith(`${basePath}/`)) {
|
if (window && window.location.pathname.startsWith(`${basePath}/`)) {
|
||||||
if (window.location.search) {
|
if (window.location.search) {
|
||||||
const oldQuery: EntityQuery = parseQueryUrl(window.location.search);
|
const oldQuery: EntityQuery = parseQueryUrl(window.location.search);
|
||||||
|
@ -48,7 +43,7 @@ export function gotoEntityQuery(query: EntityQuery, basePath: string): void {
|
||||||
*
|
*
|
||||||
* Converts TRPC errors to SvelteKit ones
|
* Converts TRPC errors to SvelteKit ones
|
||||||
*/
|
*/
|
||||||
export async function loadWrap<T>(f: () => Promise<T>): Promise<T> {
|
export async function loadWrap<T>(f: () => Promise<T>) {
|
||||||
try {
|
try {
|
||||||
return await f();
|
return await f();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -82,16 +77,16 @@ export class Debouncer {
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void {
|
clear() {
|
||||||
if (this.timeout) window.clearTimeout(this.timeout);
|
if (this.timeout) window.clearTimeout(this.timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger(): void {
|
trigger() {
|
||||||
this.clear();
|
this.clear();
|
||||||
this.timeout = window.setTimeout(this.handler, this.delay);
|
this.timeout = window.setTimeout(this.handler, this.delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
now(): void {
|
now() {
|
||||||
this.clear();
|
this.clear();
|
||||||
this.handler();
|
this.handler();
|
||||||
}
|
}
|
||||||
|
@ -112,45 +107,3 @@ export function divFloor(a: number, b: number): number {
|
||||||
export function normalizeLineEndings(s: string): string {
|
export function normalizeLineEndings(s: string): string {
|
||||||
return s.replaceAll("\r\n", "\n");
|
return s.replaceAll("\r\n", "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function superformConfig(entity?: string): Pick<FormOptions, "onError" | "onResult"> {
|
|
||||||
return {
|
|
||||||
onError: ({ result }) => {
|
|
||||||
toastError(result.error.message);
|
|
||||||
},
|
|
||||||
onResult: ({ result }) => {
|
|
||||||
if (result.type === "success") {
|
|
||||||
if (result.data?.form && typeof result.data.form.message === "string") {
|
|
||||||
toastInfo(result.data.form.message);
|
|
||||||
} else if (entity) {
|
|
||||||
toastInfo(entity + " aktualisiert");
|
|
||||||
} else {
|
|
||||||
toastInfo("OK");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isDefaultFilter(name: string): boolean {
|
|
||||||
return name.toLowerCase() === DEFAULT_FILTER_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function defaultFilterUrl(
|
|
||||||
savedFilters: Record<string, SavedFilter[]>,
|
|
||||||
view: string,
|
|
||||||
): string {
|
|
||||||
let df = undefined;
|
|
||||||
const filters = savedFilters[view];
|
|
||||||
if (filters) df = filters.find((f) => isDefaultFilter(f.name));
|
|
||||||
return "/" + view + (df ? "?" + df.query : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function defaultVisitUrl(): string {
|
|
||||||
return getQueryUrl({
|
|
||||||
filter: {
|
|
||||||
done: false,
|
|
||||||
date: [{ id: new DateRange(null, DateRange.thisWeek().end).toString() }],
|
|
||||||
},
|
|
||||||
}, URL_VISIT);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { derived, writable, type Writable } from "svelte/store";
|
|
||||||
|
|
||||||
import type { SavedFilter } from "$lib/shared/model";
|
|
||||||
|
|
||||||
// Width of the main section of the layout
|
|
||||||
export const screenWidth = writable(0);
|
|
||||||
export const screenWidthSmall = derived(screenWidth, ($mainWidth) => $mainWidth < 500);
|
|
||||||
|
|
||||||
export const savedFilters: Writable<Record<string, SavedFilter[]>> = writable({});
|
|
5
src/lib/stores/layout.ts
Normal file
5
src/lib/stores/layout.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { derived, writable } from "svelte/store";
|
||||||
|
|
||||||
|
// Width of the main section of the layout
|
||||||
|
export const screenWidth = writable(0);
|
||||||
|
export const screenWidthSmall = derived(screenWidth, ($mainWidth) => $mainWidth < 500);
|
|
@ -1,23 +1,15 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from "$app/stores";
|
import { page } from "$app/stores";
|
||||||
import type { LayoutData } from "./$types";
|
|
||||||
|
|
||||||
import { mdiAccount, mdiHome } from "@mdi/js";
|
import { mdiAccount, mdiHome } from "@mdi/js";
|
||||||
|
|
||||||
import { defaultFilterUrl, defaultVisitUrl } from "$lib/shared/util";
|
|
||||||
|
|
||||||
import Icon from "$lib/components/ui/Icon.svelte";
|
import Icon from "$lib/components/ui/Icon.svelte";
|
||||||
import NavLink from "$lib/components/ui/NavLink.svelte";
|
import NavLink from "$lib/components/ui/NavLink.svelte";
|
||||||
import { savedFilters } from "$lib/stores";
|
|
||||||
|
|
||||||
export let data: LayoutData;
|
|
||||||
|
|
||||||
$: savedFilters.set(data.savedFilters);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="sticky top-0 z-30 flex h-12 w-full
|
class="sticky top-0 z-30 flex h-12 w-full
|
||||||
justify-center bg-neutral text-neutral-content"
|
justify-center bg-neutral"
|
||||||
>
|
>
|
||||||
<nav class="navbar w-full min-h-12">
|
<nav class="navbar w-full min-h-12">
|
||||||
<div class="flex flex-1">
|
<div class="flex flex-1">
|
||||||
|
@ -31,13 +23,13 @@
|
||||||
>
|
>
|
||||||
<NavLink
|
<NavLink
|
||||||
active={$page.route.id === "/(app)/plan"}
|
active={$page.route.id === "/(app)/plan"}
|
||||||
href={defaultFilterUrl($savedFilters, "plan")}
|
href="/plan"
|
||||||
>Planung</NavLink
|
>Planung</NavLink
|
||||||
>
|
>
|
||||||
<NavLink active={$page.route.id === "/(app)/visit"} href={defaultVisitUrl()}>Visite</NavLink>
|
<NavLink active={$page.route.id === "/(app)/visit"} href="/visit">Visite</NavLink>
|
||||||
<NavLink
|
<NavLink
|
||||||
active={$page.route.id === "/(app)/patients"}
|
active={$page.route.id === "/(app)/patients"}
|
||||||
href={defaultFilterUrl($savedFilters, "patients")}
|
href="/patients"
|
||||||
>Patienten</NavLink
|
>Patienten</NavLink
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,14 +42,12 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
<ul
|
<ul
|
||||||
class="dropdown-content bg-base-100 text-base-content
|
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52"
|
||||||
z-[1] menu p-2 shadow border border-base-content/30 rounded-btn w-52"
|
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<li><NavLink active={$page.route.id === "/(app)/stations"} ddn href="/stations">Stationen</NavLink></li>
|
<li><a href="/stations">Stationen</a></li>
|
||||||
<li><NavLink active={$page.route.id === "/(app)/rooms"} ddn href="/rooms">Zimmer</NavLink></li>
|
<li><a href="/rooms">Zimmer</a></li>
|
||||||
<li><NavLink active={$page.route.id === "/(app)/categories"} ddn href="/categories">Kategorien</NavLink></li>
|
<li><a href="/categories">Kategorien</a></li>
|
||||||
<li><NavLink active={$page.route.id === "/(app)/about"} ddn href="/about">Info</NavLink></li>
|
|
||||||
<li><a href="/logout">Abmelden</a></li>
|
<li><a href="/logout">Abmelden</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
import type { LayoutLoad } from "./$types";
|
|
||||||
|
|
||||||
import { trpc } from "$lib/shared/trpc";
|
|
||||||
|
|
||||||
export const load: LayoutLoad = async (event) => {
|
|
||||||
const savedFilters = await trpc(event).savedFilter.getAll.query();
|
|
||||||
|
|
||||||
return { savedFilters };
|
|
||||||
};
|
|
|
@ -2,10 +2,6 @@
|
||||||
import { page } from "$app/stores";
|
import { page } from "$app/stores";
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
import { defaultFilterUrl } from "$lib/shared/util";
|
|
||||||
|
|
||||||
import { savedFilters } from "$lib/stores";
|
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -26,7 +22,7 @@
|
||||||
<h2 class="card-title">Planung</h2>
|
<h2 class="card-title">Planung</h2>
|
||||||
<p>Hier können sie neue Visitenbucheinträge erstellen.</p>
|
<p>Hier können sie neue Visitenbucheinträge erstellen.</p>
|
||||||
<div class="card-actions justify-end">
|
<div class="card-actions justify-end">
|
||||||
<a class="btn btn-primary" href={defaultFilterUrl($savedFilters, "plan")}>Planung</a>
|
<a class="btn btn-primary" href="/plan">Planung</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
const version = __VERSION__;
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
const lastmod = __LASTMOD__;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<h1 class="heading">Visitenbuch</h1>
|
|
||||||
<p>Version: {version}</p>
|
|
||||||
<p>Letzte Änderung: {lastmod}</p>
|
|
||||||
<p><a href="/oss-licenses.html">Open-Source-Lizenzen</a></p>
|
|
||||||
</div>
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
import CategoryField from "$lib/components/table/CategoryField.svelte";
|
import CategoryField from "$lib/components/table/CategoryField.svelte";
|
||||||
import Header from "$lib/components/ui/Header.svelte";
|
import Header from "$lib/components/ui/Header.svelte";
|
||||||
import HiddenToggle from "$lib/components/ui/HiddenToggle.svelte";
|
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
</script>
|
</script>
|
||||||
|
@ -12,11 +11,8 @@
|
||||||
<title>Kategorien</title>
|
<title>Kategorien</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<Header title={"Kategorien" + (data.hidden ? " (ausgeblendet)" : "")}>
|
<Header title="Kategorien">
|
||||||
<div slot="rightBtn" class="flex gap-2 ml-auto">
|
<a slot="rightBtn" class="btn btn-sm btn-primary ml-auto" href="/category/new">Neue Kategorie</a>
|
||||||
<HiddenToggle baseUrl="/categories" hidden={data.hidden} />
|
|
||||||
<a class="btn btn-sm btn-primary" href="/category/new">Neue Kategorie</a>
|
|
||||||
</div>
|
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
|
|
|
@ -5,8 +5,7 @@ import { loadWrap } from "$lib/shared/util";
|
||||||
|
|
||||||
export const load: PageLoad = async (event) => {
|
export const load: PageLoad = async (event) => {
|
||||||
return loadWrap(async () => {
|
return loadWrap(async () => {
|
||||||
const hidden = event.url.searchParams.get("hidden") !== null;
|
const categories = await trpc(event).category.list.query();
|
||||||
const categories = await trpc(event).category.list.query({ hidden });
|
return { categories };
|
||||||
return { categories, hidden };
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Actions } from "./$types";
|
import type { Actions } from "./$types";
|
||||||
|
|
||||||
import { fail, redirect } from "@sveltejs/kit";
|
import { fail, redirect } from "@sveltejs/kit";
|
||||||
import { message, superValidate } from "sveltekit-superforms";
|
import { superValidate } from "sveltekit-superforms";
|
||||||
import { zod } from "sveltekit-superforms/adapters";
|
import { zod } from "sveltekit-superforms/adapters";
|
||||||
|
|
||||||
import { ZCategoryNew, ZUrlEntityId } from "$lib/shared/model/validation";
|
import { ZCategoryNew, ZUrlEntityId } from "$lib/shared/model/validation";
|
||||||
|
@ -12,18 +12,14 @@ export const actions: Actions = {
|
||||||
default: async (event) => loadWrap(async () => {
|
default: async (event) => loadWrap(async () => {
|
||||||
const id = ZUrlEntityId.parse(event.params.id);
|
const id = ZUrlEntityId.parse(event.params.id);
|
||||||
const formData = await event.request.formData();
|
const formData = await event.request.formData();
|
||||||
const form = await superValidate(formData, zod(ZCategoryNew));
|
|
||||||
|
|
||||||
const hide = formData.get("hide");
|
|
||||||
const del = formData.get("delete");
|
const del = formData.get("delete");
|
||||||
if (hide) {
|
if (del) {
|
||||||
const hidden = Boolean(parseInt(hide.toString()));
|
|
||||||
await trpc(event).category.hide.mutate({ id, hidden });
|
|
||||||
return message(form, "Kategorie " + (hidden ? "ausgeblendet" : "eingeblendet"));
|
|
||||||
} else if (del) {
|
|
||||||
await trpc(event).category.delete.mutate(id);
|
await trpc(event).category.delete.mutate(id);
|
||||||
redirect(302, "/categories");
|
redirect(302, "/categories");
|
||||||
} else {
|
} else {
|
||||||
|
const form = await superValidate(formData, zod(ZCategoryNew));
|
||||||
|
|
||||||
if (!form.valid) {
|
if (!form.valid) {
|
||||||
return fail(400, { form });
|
return fail(400, { form });
|
||||||
}
|
}
|
||||||
|
@ -32,7 +28,8 @@ export const actions: Actions = {
|
||||||
id,
|
id,
|
||||||
category: form.data,
|
category: form.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return { form };
|
||||||
}
|
}
|
||||||
return { form };
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
import CategoryForm from "$lib/components/form/CategoryForm.svelte";
|
import CategoryForm from "$lib/components/form/CategoryForm.svelte";
|
||||||
import HideDelete from "$lib/components/form/HideDelete.svelte";
|
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
</script>
|
</script>
|
||||||
|
@ -12,5 +11,5 @@
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<CategoryForm category={data.category} formData={data.form}>
|
<CategoryForm category={data.category} formData={data.form}>
|
||||||
<HideDelete hasEntries={data.category.n_entries > 0} hidden={data.category.hidden} />
|
<button name="delete" class="btn btn-error" type="submit" value="1">Löschen</button>
|
||||||
</CategoryForm>
|
</CategoryForm>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Actions } from "./$types";
|
import type { Actions } from "./$types";
|
||||||
|
|
||||||
import { fail } from "@sveltejs/kit";
|
import { fail } from "@sveltejs/kit";
|
||||||
import { superValidate, message } from "sveltekit-superforms";
|
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";
|
||||||
|
@ -22,6 +22,5 @@ export const actions: Actions = {
|
||||||
old_execution_id: null,
|
old_execution_id: null,
|
||||||
execution: { text: form.data.text },
|
execution: { text: form.data.text },
|
||||||
});
|
});
|
||||||
return message(form, "Eintrag erledigt");
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
import { defaults, superForm } from "sveltekit-superforms";
|
import { defaults, superForm } from "sveltekit-superforms";
|
||||||
|
|
||||||
import { superformConfig } from "$lib/shared/util";
|
|
||||||
|
|
||||||
import EntryBody from "$lib/components/entry/EntryBody.svelte";
|
import EntryBody from "$lib/components/entry/EntryBody.svelte";
|
||||||
import MarkdownInput from "$lib/components/ui/markdown/MarkdownInput.svelte";
|
import MarkdownInput from "$lib/components/ui/markdown/MarkdownInput.svelte";
|
||||||
|
|
||||||
|
@ -14,11 +12,8 @@
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
const formData = defaults(SchemaEntryExecution);
|
const formData = defaults(SchemaEntryExecution);
|
||||||
const {
|
const { form, errors, enhance } = superForm(formData, {
|
||||||
form, errors, enhance,
|
|
||||||
} = superForm(formData, {
|
|
||||||
validators: SchemaEntryExecution,
|
validators: SchemaEntryExecution,
|
||||||
...superformConfig("Eintrag"),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
import { trpc } from "$lib/shared/trpc";
|
import { trpc } from "$lib/shared/trpc";
|
||||||
import { formatDate, humanDate } from "$lib/shared/util";
|
import { formatDate, humanDate } from "$lib/shared/util";
|
||||||
import { superformConfig } from "$lib/shared/util";
|
|
||||||
|
|
||||||
import PatientCard from "$lib/components/entry/PatientCard.svelte";
|
import PatientCard from "$lib/components/entry/PatientCard.svelte";
|
||||||
import Autocomplete from "$lib/components/filter/Autocomplete.svelte";
|
import Autocomplete from "$lib/components/filter/Autocomplete.svelte";
|
||||||
|
@ -25,7 +24,6 @@
|
||||||
form, errors, constraints, enhance, tainted,
|
form, errors, constraints, enhance, tainted,
|
||||||
} = superForm(data.form, {
|
} = superForm(data.form, {
|
||||||
validators: SchemaNewEntryVersion,
|
validators: SchemaNewEntryVersion,
|
||||||
...superformConfig("Eintrag"),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
import { defaults, superForm } from "sveltekit-superforms";
|
import { defaults, superForm } from "sveltekit-superforms";
|
||||||
|
|
||||||
import { superformConfig } from "$lib/shared/util";
|
|
||||||
|
|
||||||
import EntryBody from "$lib/components/entry/EntryBody.svelte";
|
import EntryBody from "$lib/components/entry/EntryBody.svelte";
|
||||||
import MarkdownInput from "$lib/components/ui/markdown/MarkdownInput.svelte";
|
import MarkdownInput from "$lib/components/ui/markdown/MarkdownInput.svelte";
|
||||||
|
|
||||||
|
@ -16,7 +14,6 @@
|
||||||
const formData = defaults(SchemaNewExecution);
|
const formData = defaults(SchemaNewExecution);
|
||||||
const { form, errors, enhance } = superForm(formData, {
|
const { form, errors, enhance } = superForm(formData, {
|
||||||
validators: SchemaNewExecution,
|
validators: SchemaNewExecution,
|
||||||
...superformConfig("Eintrag"),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
import { defaults, superForm } from "sveltekit-superforms";
|
import { defaults, superForm } from "sveltekit-superforms";
|
||||||
|
|
||||||
import { trpc } from "$lib/shared/trpc";
|
import { trpc } from "$lib/shared/trpc";
|
||||||
import { superformConfig } from "$lib/shared/util";
|
|
||||||
import { toastError } from "$lib/shared/util/toast";
|
|
||||||
|
|
||||||
import Autocomplete from "$lib/components/filter/Autocomplete.svelte";
|
import Autocomplete from "$lib/components/filter/Autocomplete.svelte";
|
||||||
import FormField from "$lib/components/ui/FormField.svelte";
|
import FormField from "$lib/components/ui/FormField.svelte";
|
||||||
|
@ -16,7 +14,6 @@
|
||||||
form, errors, constraints, enhance,
|
form, errors, constraints, enhance,
|
||||||
} = superForm(formData, {
|
} = superForm(formData, {
|
||||||
validators: SchemaNewEntryWithPatient,
|
validators: SchemaNewEntryWithPatient,
|
||||||
...superformConfig("Eintrag"),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -59,7 +56,7 @@
|
||||||
$form.patient_first_name = p.first_name;
|
$form.patient_first_name = p.first_name;
|
||||||
$form.patient_last_name = p.last_name;
|
$form.patient_last_name = p.last_name;
|
||||||
$form.patient_age = p.age;
|
$form.patient_age = p.age;
|
||||||
}).catch((e) => toastError("Konnte Patient nicht laden:\n" + e));
|
});
|
||||||
return { newValue: item.name ?? "", close: true };
|
return { newValue: item.name ?? "", close: true };
|
||||||
}}
|
}}
|
||||||
onUnselect={() => {
|
onUnselect={() => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Actions } from "./$types";
|
import type { Actions } from "./$types";
|
||||||
|
|
||||||
import { fail, redirect } from "@sveltejs/kit";
|
import { fail, redirect } from "@sveltejs/kit";
|
||||||
import { message, superValidate } from "sveltekit-superforms";
|
import { superValidate } from "sveltekit-superforms";
|
||||||
import { zod } from "sveltekit-superforms/adapters";
|
import { zod } from "sveltekit-superforms/adapters";
|
||||||
|
|
||||||
import { ZPatientNew, ZUrlEntityId } from "$lib/shared/model/validation";
|
import { ZPatientNew, ZUrlEntityId } from "$lib/shared/model/validation";
|
||||||
|
@ -12,18 +12,20 @@ export const actions: Actions = {
|
||||||
default: async (event) => loadWrap(async () => {
|
default: async (event) => loadWrap(async () => {
|
||||||
const id = ZUrlEntityId.parse(event.params.id);
|
const id = ZUrlEntityId.parse(event.params.id);
|
||||||
const formData = await event.request.formData();
|
const formData = await event.request.formData();
|
||||||
const form = await superValidate(formData, zod(ZPatientNew));
|
|
||||||
|
|
||||||
const hide = formData.get("hide");
|
const hide = formData.get("hide");
|
||||||
const del = formData.get("delete");
|
const del = formData.get("delete");
|
||||||
if (hide) {
|
if (hide) {
|
||||||
const hidden = Boolean(parseInt(hide.toString()));
|
await trpc(event).patient.hide.mutate({
|
||||||
await trpc(event).patient.hide.mutate({ id, hidden });
|
id,
|
||||||
return message(form, "Patient " + (hidden ? "ausgeblendet" : "eingeblendet"));
|
hidden: Boolean(parseInt(hide.toString())),
|
||||||
|
});
|
||||||
} else if (del) {
|
} else if (del) {
|
||||||
await trpc(event).patient.delete.mutate(id);
|
await trpc(event).patient.delete.mutate(id);
|
||||||
redirect(302, "/patients");
|
redirect(302, "/patients");
|
||||||
} else {
|
} else {
|
||||||
|
const form = await superValidate(formData, zod(ZPatientNew));
|
||||||
|
|
||||||
if (!form.valid) {
|
if (!form.valid) {
|
||||||
return fail(400, { form });
|
return fail(400, { form });
|
||||||
}
|
}
|
||||||
|
@ -32,7 +34,8 @@ export const actions: Actions = {
|
||||||
id,
|
id,
|
||||||
patient: form.data,
|
patient: form.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return { form };
|
||||||
}
|
}
|
||||||
return { form };
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
import HideDelete from "$lib/components/form/HideDelete.svelte";
|
|
||||||
import PatientForm from "$lib/components/form/PatientForm.svelte";
|
import PatientForm from "$lib/components/form/PatientForm.svelte";
|
||||||
import FilteredEntryTable from "$lib/components/table/FilteredEntryTable.svelte";
|
import FilteredEntryTable from "$lib/components/table/FilteredEntryTable.svelte";
|
||||||
|
|
||||||
|
@ -15,7 +14,19 @@
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<PatientForm formData={data.form} patient={data.patient}>
|
<PatientForm formData={data.form} patient={data.patient}>
|
||||||
<HideDelete hasEntries={data.patient.n_entries > 0} hidden={data.patient.hidden} />
|
{#if data.patient.hidden}
|
||||||
|
<button
|
||||||
|
name="hide"
|
||||||
|
class="btn btn-primary"
|
||||||
|
type="submit"
|
||||||
|
value="0"
|
||||||
|
>Einblenden</button
|
||||||
|
>
|
||||||
|
{:else if hasEntries}
|
||||||
|
<button name="hide" class="btn" type="submit" value="1">Ausblenden</button>
|
||||||
|
{:else}
|
||||||
|
<button name="delete" class="btn btn-error" type="submit" value="1">Löschen</button>
|
||||||
|
{/if}
|
||||||
</PatientForm>
|
</PatientForm>
|
||||||
|
|
||||||
{#if hasEntries}
|
{#if hasEntries}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Actions } from "./$types";
|
import type { Actions } from "./$types";
|
||||||
|
|
||||||
import { fail, redirect } from "@sveltejs/kit";
|
import { fail, redirect } from "@sveltejs/kit";
|
||||||
import { message, superValidate } from "sveltekit-superforms";
|
import { superValidate } from "sveltekit-superforms";
|
||||||
import { zod } from "sveltekit-superforms/adapters";
|
import { zod } from "sveltekit-superforms/adapters";
|
||||||
|
|
||||||
import { ZRoomNew, ZUrlEntityId } from "$lib/shared/model/validation";
|
import { ZRoomNew, ZUrlEntityId } from "$lib/shared/model/validation";
|
||||||
|
@ -12,18 +12,14 @@ export const actions: Actions = {
|
||||||
default: async (event) => loadWrap(async () => {
|
default: async (event) => loadWrap(async () => {
|
||||||
const id = ZUrlEntityId.parse(event.params.id);
|
const id = ZUrlEntityId.parse(event.params.id);
|
||||||
const formData = await event.request.formData();
|
const formData = await event.request.formData();
|
||||||
const form = await superValidate(formData, zod(ZRoomNew));
|
|
||||||
|
|
||||||
const hide = formData.get("hide");
|
|
||||||
const del = formData.get("delete");
|
const del = formData.get("delete");
|
||||||
if (hide) {
|
if (del) {
|
||||||
const hidden = Boolean(parseInt(hide.toString()));
|
|
||||||
await trpc(event).room.hide.mutate({ id, hidden });
|
|
||||||
return message(form, "Zimmer " + (hidden ? "ausgeblendet" : "eingeblendet"));
|
|
||||||
} else if (del) {
|
|
||||||
await trpc(event).room.delete.mutate(id);
|
await trpc(event).room.delete.mutate(id);
|
||||||
redirect(302, "/rooms");
|
redirect(302, "/rooms");
|
||||||
} else {
|
} else {
|
||||||
|
const form = await superValidate(formData, zod(ZRoomNew));
|
||||||
|
|
||||||
if (!form.valid) {
|
if (!form.valid) {
|
||||||
return fail(400, { form });
|
return fail(400, { form });
|
||||||
}
|
}
|
||||||
|
@ -32,7 +28,8 @@ export const actions: Actions = {
|
||||||
id,
|
id,
|
||||||
room: form.data,
|
room: form.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return { form };
|
||||||
}
|
}
|
||||||
return { form };
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
import HideDelete from "$lib/components/form/HideDelete.svelte";
|
|
||||||
import RoomForm from "$lib/components/form/RoomForm.svelte";
|
import RoomForm from "$lib/components/form/RoomForm.svelte";
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
@ -12,5 +11,5 @@
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<RoomForm formData={data.form} room={data.room}>
|
<RoomForm formData={data.form} room={data.room}>
|
||||||
<HideDelete hasEntries={data.room.n_patients > 0} hidden={data.room.hidden} />
|
<button name="delete" class="btn btn-error" type="submit" value="1">Löschen</button>
|
||||||
</RoomForm>
|
</RoomForm>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
import Header from "$lib/components/ui/Header.svelte";
|
import Header from "$lib/components/ui/Header.svelte";
|
||||||
import HiddenToggle from "$lib/components/ui/HiddenToggle.svelte";
|
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
</script>
|
</script>
|
||||||
|
@ -11,11 +10,8 @@
|
||||||
<title>Zimmer</title>
|
<title>Zimmer</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<Header title={"Zimmer" + (data.hidden ? " (ausgeblendet)" : "")}>
|
<Header title="Zimmer">
|
||||||
<div slot="rightBtn" class="flex gap-2 ml-auto">
|
<a slot="rightBtn" class="btn btn-sm btn-primary ml-auto" href="/room/new">Neues Zimmer</a>
|
||||||
<HiddenToggle baseUrl="/rooms" hidden={data.hidden} />
|
|
||||||
<a class="btn btn-sm btn-primary ml-auto" href="/room/new">Neues Zimmer</a>
|
|
||||||
</div>
|
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
|
|
|
@ -5,8 +5,7 @@ import { loadWrap } from "$lib/shared/util";
|
||||||
|
|
||||||
export const load: PageLoad = async (event) => {
|
export const load: PageLoad = async (event) => {
|
||||||
return loadWrap(async () => {
|
return loadWrap(async () => {
|
||||||
const hidden = event.url.searchParams.get("hidden") !== null;
|
const rooms = await trpc(event).room.list.query();
|
||||||
const rooms = await trpc(event).room.list.query({ hidden });
|
return { rooms };
|
||||||
return { rooms, hidden };
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Actions } from "./$types";
|
import type { Actions } from "./$types";
|
||||||
|
|
||||||
import { fail, redirect } from "@sveltejs/kit";
|
import { fail, redirect } from "@sveltejs/kit";
|
||||||
import { message, superValidate } from "sveltekit-superforms";
|
import { superValidate } from "sveltekit-superforms";
|
||||||
import { zod } from "sveltekit-superforms/adapters";
|
import { zod } from "sveltekit-superforms/adapters";
|
||||||
|
|
||||||
import { ZStationNew, ZUrlEntityId } from "$lib/shared/model/validation";
|
import { ZStationNew, ZUrlEntityId } from "$lib/shared/model/validation";
|
||||||
|
@ -12,18 +12,14 @@ export const actions: Actions = {
|
||||||
default: async (event) => loadWrap(async () => {
|
default: async (event) => loadWrap(async () => {
|
||||||
const id = ZUrlEntityId.parse(event.params.id);
|
const id = ZUrlEntityId.parse(event.params.id);
|
||||||
const formData = await event.request.formData();
|
const formData = await event.request.formData();
|
||||||
const form = await superValidate(formData, zod(ZStationNew));
|
|
||||||
|
|
||||||
const hide = formData.get("hide");
|
|
||||||
const del = formData.get("delete");
|
const del = formData.get("delete");
|
||||||
if (hide) {
|
if (del) {
|
||||||
const hidden = Boolean(parseInt(hide.toString()));
|
|
||||||
await trpc(event).station.hide.mutate({ id, hidden });
|
|
||||||
return message(form, "Station " + (hidden ? "ausgeblendet" : "eingeblendet"));
|
|
||||||
} else if (del) {
|
|
||||||
await trpc(event).station.delete.mutate(id);
|
await trpc(event).station.delete.mutate(id);
|
||||||
redirect(302, "/stations");
|
redirect(302, "/stations");
|
||||||
} else {
|
} else {
|
||||||
|
const form = await superValidate(formData, zod(ZStationNew));
|
||||||
|
|
||||||
if (!form.valid) {
|
if (!form.valid) {
|
||||||
return fail(400, { form });
|
return fail(400, { form });
|
||||||
}
|
}
|
||||||
|
@ -32,7 +28,8 @@ export const actions: Actions = {
|
||||||
id,
|
id,
|
||||||
station: form.data,
|
station: form.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return { form };
|
||||||
}
|
}
|
||||||
return { form };
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
import HideDelete from "$lib/components/form/HideDelete.svelte";
|
|
||||||
import StationForm from "$lib/components/form/StationForm.svelte";
|
import StationForm from "$lib/components/form/StationForm.svelte";
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
@ -12,5 +11,5 @@
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<StationForm formData={data.form} station={data.station}>
|
<StationForm formData={data.form} station={data.station}>
|
||||||
<HideDelete hasEntries={data.station.n_rooms > 0} hidden={data.station.hidden} />
|
<button name="delete" class="btn btn-error" type="submit" value="1">Löschen</button>
|
||||||
</StationForm>
|
</StationForm>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
import Header from "$lib/components/ui/Header.svelte";
|
import Header from "$lib/components/ui/Header.svelte";
|
||||||
import HiddenToggle from "$lib/components/ui/HiddenToggle.svelte";
|
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
</script>
|
</script>
|
||||||
|
@ -11,11 +10,8 @@
|
||||||
<title>Stationen</title>
|
<title>Stationen</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<Header title={"Stationen" + (data.hidden ? " (ausgeblendet)" : "")}>
|
<Header title="Stationen">
|
||||||
<div slot="rightBtn" class="flex gap-2 ml-auto">
|
<a slot="rightBtn" class="btn btn-sm btn-primary ml-auto" href="/station/new">Neue Station</a>
|
||||||
<HiddenToggle baseUrl="/stations" hidden={data.hidden} />
|
|
||||||
<a class="btn btn-sm btn-primary ml-auto" href="/station/new">Neue Station</a>
|
|
||||||
</div>
|
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
|
|
|
@ -5,8 +5,7 @@ import { loadWrap } from "$lib/shared/util";
|
||||||
|
|
||||||
export const load: PageLoad = async (event) => {
|
export const load: PageLoad = async (event) => {
|
||||||
return loadWrap(async () => {
|
return loadWrap(async () => {
|
||||||
const hidden = event.url.searchParams.get("hidden") !== null;
|
const stations = await trpc(event).station.list.query();
|
||||||
const stations = await trpc(event).station.list.query({ hidden });
|
return { stations };
|
||||||
return { stations, hidden };
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { toast } from "@zerodevx/svelte-toast";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button class="btn" on:click={() => toast.push({ msg: "Hello" })}>Ok</button>
|
|
||||||
<button class="btn" on:click={() => toast.push({ msg: "Error", classes: ["toast-error"] })}>Error</button>
|
|
||||||
</div>
|
|
|
@ -4,8 +4,9 @@
|
||||||
import type { PageData } from "./$types";
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
import { URL_VISIT } from "$lib/shared/constants";
|
import { URL_VISIT } from "$lib/shared/constants";
|
||||||
import type { PaginationRequest, Station } from "$lib/shared/model";
|
import type { PaginationRequest } from "$lib/shared/model";
|
||||||
import { trpc } from "$lib/shared/trpc";
|
import { trpc } from "$lib/shared/trpc";
|
||||||
|
import type { RouterOutput } from "$lib/shared/trpc";
|
||||||
import { DateRange, getQueryUrl } from "$lib/shared/util";
|
import { DateRange, getQueryUrl } from "$lib/shared/util";
|
||||||
|
|
||||||
import Autocomplete from "$lib/components/filter/Autocomplete.svelte";
|
import Autocomplete from "$lib/components/filter/Autocomplete.svelte";
|
||||||
|
@ -16,7 +17,7 @@
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
let dateRange: DateRange;
|
let dateRange: DateRange;
|
||||||
let selection: Station | null;
|
let selection: RouterOutput["station"]["get"] | null;
|
||||||
|
|
||||||
$: if (data.query.filter?.date) {
|
$: if (data.query.filter?.date) {
|
||||||
const date = data.query.filter?.date[0];
|
const date = data.query.filter?.date[0];
|
||||||
|
@ -40,14 +41,14 @@
|
||||||
selection = null;
|
selection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function paginationUpdate(pagination: PaginationRequest): void {
|
function paginationUpdate(pagination: PaginationRequest) {
|
||||||
updateQuery({
|
updateQuery({
|
||||||
filter: data.query.filter,
|
filter: data.query.filter,
|
||||||
pagination,
|
pagination,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterUpdate(): void {
|
function filterUpdate() {
|
||||||
updateQuery({
|
updateQuery({
|
||||||
filter: {
|
filter: {
|
||||||
done: false,
|
done: false,
|
||||||
|
@ -57,7 +58,7 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateQuery(q: typeof data.query): void {
|
function updateQuery(q: typeof data.query) {
|
||||||
if (browser) {
|
if (browser) {
|
||||||
// Update page URL
|
// Update page URL
|
||||||
const url = getQueryUrl(q, URL_VISIT);
|
const url = getQueryUrl(q, URL_VISIT);
|
||||||
|
|
|
@ -3,9 +3,12 @@ import type { PageLoad } from "./$types";
|
||||||
import { redirect } from "@sveltejs/kit";
|
import { redirect } from "@sveltejs/kit";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { URL_VISIT } from "$lib/shared/constants";
|
||||||
import { ZEntriesQuery } from "$lib/shared/model/validation";
|
import { ZEntriesQuery } from "$lib/shared/model/validation";
|
||||||
import { trpc } from "$lib/shared/trpc";
|
import { trpc } from "$lib/shared/trpc";
|
||||||
import { defaultVisitUrl, loadWrap, parseQueryUrl } from "$lib/shared/util";
|
import {
|
||||||
|
DateRange, getQueryUrl, loadWrap, parseQueryUrl,
|
||||||
|
} from "$lib/shared/util";
|
||||||
|
|
||||||
export const load: PageLoad = async (event) => {
|
export const load: PageLoad = async (event) => {
|
||||||
return loadWrap(async () => {
|
return loadWrap(async () => {
|
||||||
|
@ -17,7 +20,13 @@ export const load: PageLoad = async (event) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!query.filter) {
|
if (!query.filter) {
|
||||||
redirect(302, defaultVisitUrl());
|
const url = getQueryUrl({
|
||||||
|
filter: {
|
||||||
|
done: false,
|
||||||
|
date: [{ id: DateRange.thisWeek().toString() }],
|
||||||
|
},
|
||||||
|
}, URL_VISIT);
|
||||||
|
redirect(302, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort entries by date
|
// Sort entries by date
|
||||||
|
|
|
@ -3,10 +3,8 @@
|
||||||
|
|
||||||
import { navigating } from "$app/stores";
|
import { navigating } from "$app/stores";
|
||||||
|
|
||||||
import { SvelteToast } from "@zerodevx/svelte-toast";
|
|
||||||
|
|
||||||
import LoadingBar from "$lib/components/ui/LoadingBar.svelte";
|
import LoadingBar from "$lib/components/ui/LoadingBar.svelte";
|
||||||
import { screenWidth } from "$lib/stores";
|
import { screenWidth } from "$lib/stores/layout";
|
||||||
|
|
||||||
let loadingBar: LoadingBar | undefined;
|
let loadingBar: LoadingBar | undefined;
|
||||||
|
|
||||||
|
@ -15,12 +13,9 @@
|
||||||
} else {
|
} else {
|
||||||
loadingBar?.reset();
|
loadingBar?.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = { pausable: true };
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LoadingBar bind:this={loadingBar} cls="fixed top-0 left-0 z-50" />
|
<LoadingBar bind:this={loadingBar} cls="fixed top-0 left-0 z-50" />
|
||||||
<SvelteToast {options} />
|
|
||||||
|
|
||||||
<div class="bg-base-100 text-base-content" bind:clientWidth={$screenWidth}>
|
<div class="bg-base-100 text-base-content" bind:clientWidth={$screenWidth}>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { makeAuthjsRequest } from "$lib/server/auth";
|
||||||
*/
|
*/
|
||||||
const COOKIE_NAME = "autoLoginTs";
|
const COOKIE_NAME = "autoLoginTs";
|
||||||
|
|
||||||
async function doLogin(event: RequestEvent): Promise<never> {
|
async function doLogin(event: RequestEvent) {
|
||||||
const callbackUrl = event.url.searchParams.get("returnURL") ?? baseUrl(event.url);
|
const callbackUrl = event.url.searchParams.get("returnURL") ?? baseUrl(event.url);
|
||||||
|
|
||||||
return makeAuthjsRequest(event, "signin/keycloak", { callbackUrl });
|
return makeAuthjsRequest(event, "signin/keycloak", { callbackUrl });
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
/** @type {import('tailwindcss').Config}*/
|
/** @type {import('tailwindcss').Config}*/
|
||||||
|
|
||||||
const themes = require("daisyui/src/theming/themes");
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
content: [
|
content: [
|
||||||
"./src/**/*.{html,js,svelte,ts}",
|
"./src/**/*.{html,js,svelte,ts}",
|
||||||
|
@ -12,15 +9,6 @@ const config = {
|
||||||
|
|
||||||
daisyui: {
|
daisyui: {
|
||||||
logs: false,
|
logs: false,
|
||||||
themes: [
|
|
||||||
{
|
|
||||||
light: {
|
|
||||||
...themes["light"],
|
|
||||||
"primary": "#799dff",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dark"
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
safelist: ["prose"],
|
safelist: ["prose"],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
import { prisma } from "$lib/server/prisma";
|
import { prisma } from "$lib/server/prisma";
|
||||||
|
|
||||||
import {
|
import { CATEGORIES, STATIONS, USERS } from "./testdata";
|
||||||
CATEGORIES, ROOMS, STATIONS, USERS,
|
|
||||||
} from "./testdata";
|
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
await prisma.$transaction([
|
await prisma.$transaction([
|
||||||
prisma.savedFilter.deleteMany(),
|
|
||||||
prisma.entryExecution.deleteMany(),
|
prisma.entryExecution.deleteMany(),
|
||||||
prisma.entryVersion.deleteMany(),
|
prisma.entryVersion.deleteMany(),
|
||||||
prisma.entry.deleteMany(),
|
prisma.entry.deleteMany(),
|
||||||
|
@ -20,9 +17,11 @@ export default async () => {
|
||||||
prisma.category.createMany({ data: CATEGORIES }),
|
prisma.category.createMany({ data: CATEGORIES }),
|
||||||
prisma.station.createMany({ data: STATIONS }),
|
prisma.station.createMany({ data: STATIONS }),
|
||||||
prisma.room.createMany({
|
prisma.room.createMany({
|
||||||
data: ROOMS.map((v) => {
|
data: [
|
||||||
return { id: v.id, name: v.name, station_id: v.station.id };
|
{ id: 1, name: "R1.1", station_id: 1 },
|
||||||
}),
|
{ id: 2, name: "R1.2", station_id: 1 },
|
||||||
|
{ id: 3, name: "R2.1", station_id: 2 },
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
prisma.patient.createMany({
|
prisma.patient.createMany({
|
||||||
data: [
|
data: [
|
||||||
|
@ -56,7 +55,7 @@ export default async () => {
|
||||||
prisma.$executeRawUnsafe(
|
prisma.$executeRawUnsafe(
|
||||||
`alter sequence stations_id_seq restart with ${STATIONS.length + 1}`,
|
`alter sequence stations_id_seq restart with ${STATIONS.length + 1}`,
|
||||||
),
|
),
|
||||||
prisma.$executeRawUnsafe(`alter sequence rooms_id_seq restart with ${ROOMS.length + 1}`),
|
prisma.$executeRawUnsafe("alter sequence rooms_id_seq restart with 4"),
|
||||||
prisma.$executeRawUnsafe("alter sequence patients_id_seq restart with 4"),
|
prisma.$executeRawUnsafe("alter sequence patients_id_seq restart with 4"),
|
||||||
prisma.$executeRawUnsafe("alter sequence entry_executions_id_seq restart with 1"),
|
prisma.$executeRawUnsafe("alter sequence entry_executions_id_seq restart with 1"),
|
||||||
prisma.$executeRawUnsafe("alter sequence entry_versions_id_seq restart with 1"),
|
prisma.$executeRawUnsafe("alter sequence entry_versions_id_seq restart with 1"),
|
||||||
|
|
|
@ -1,33 +1,9 @@
|
||||||
import type { Category, Station, Room } from "$lib/shared/model";
|
import type { Category, Station } from "$lib/shared/model";
|
||||||
|
|
||||||
export const S1: Station = { id: 1, name: "S1" };
|
export const S1: Station = { id: 1, name: "S1" };
|
||||||
export const S2: Station = { id: 2, name: "S2" };
|
export const S2: Station = { id: 2, name: "S2" };
|
||||||
export const S3: Station = { id: 3, name: "S3_tmp" };
|
|
||||||
|
|
||||||
export const STATIONS: Station[] = [S1, S2, S3];
|
export const STATIONS: Station[] = [S1, S2];
|
||||||
|
|
||||||
export const ROOMS: Room[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: "R1.1",
|
|
||||||
station: S1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: "R1.2",
|
|
||||||
station: S1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: "R2.1",
|
|
||||||
station: S2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: "Rtmp",
|
|
||||||
station: S2,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const CATEGORIES: Category[] = [
|
export const CATEGORIES: Category[] = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
import {
|
import { getCategories, getCategory, newCategory } from "$lib/server/query";
|
||||||
deleteCategory, getCategories, getCategory, hideCategory, newCategory,
|
|
||||||
} from "$lib/server/query";
|
|
||||||
import { CATEGORIES } from "$tests/helpers/testdata";
|
import { CATEGORIES } from "$tests/helpers/testdata";
|
||||||
|
|
||||||
test("create category", async () => {
|
test("create category", async () => {
|
||||||
|
@ -23,19 +21,3 @@ test("get categories", async () => {
|
||||||
const categories = await getCategories();
|
const categories = await getCategories();
|
||||||
expect(categories).toStrictEqual(CATEGORIES);
|
expect(categories).toStrictEqual(CATEGORIES);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("delete categories", async () => {
|
|
||||||
await deleteCategory(6);
|
|
||||||
expect(getCategory(6)).rejects.toThrowError("No Category found");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("hide category", async () => {
|
|
||||||
await hideCategory(1, true);
|
|
||||||
const cs1 = await getCategories();
|
|
||||||
const exp = [...CATEGORIES];
|
|
||||||
exp.splice(0, 1);
|
|
||||||
expect(cs1).toStrictEqual(exp);
|
|
||||||
await hideCategory(1, false);
|
|
||||||
const cs2 = await getCategories();
|
|
||||||
expect(cs2).toStrictEqual(CATEGORIES);
|
|
||||||
});
|
|
||||||
|
|
|
@ -3,14 +3,12 @@ import { expect, test } from "vitest";
|
||||||
import { ErrorConflict } from "$lib/shared/util/error";
|
import { ErrorConflict } from "$lib/shared/util/error";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getCategoryNEntries,
|
|
||||||
getEntries,
|
getEntries,
|
||||||
getEntry,
|
getEntry,
|
||||||
getEntryNExecutions,
|
getEntryNExecutions,
|
||||||
getEntryNVersions,
|
getEntryNVersions,
|
||||||
getEntryVersions,
|
getEntryVersions,
|
||||||
getNTodo,
|
getNTodo,
|
||||||
getPatientNEntries,
|
|
||||||
newEntry,
|
newEntry,
|
||||||
newEntryExecution,
|
newEntryExecution,
|
||||||
newEntryVersion,
|
newEntryVersion,
|
||||||
|
@ -23,46 +21,6 @@ const TEST_VERSION = {
|
||||||
priority: false,
|
priority: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function insertTestEntries() {
|
|
||||||
// Create some entries
|
|
||||||
const eId1 = await newEntry(1, {
|
|
||||||
patient_id: 1,
|
|
||||||
version: TEST_VERSION,
|
|
||||||
});
|
|
||||||
const eId2 = await newEntry(1, {
|
|
||||||
patient_id: 2,
|
|
||||||
version: {
|
|
||||||
text: "Carrot cake jelly-o bonbon toffee chocolate.",
|
|
||||||
date: "2024-01-05",
|
|
||||||
priority: false,
|
|
||||||
category_id: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const eId3 = await newEntry(1, {
|
|
||||||
patient_id: 1,
|
|
||||||
version: {
|
|
||||||
text: "Cheesecake danish donut oat cake caramels.",
|
|
||||||
date: "2024-01-06",
|
|
||||||
priority: false,
|
|
||||||
category_id: null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update an entry
|
|
||||||
await newEntryVersion(2, eId1, {
|
|
||||||
category_id: 3,
|
|
||||||
text: `${TEST_VERSION.text}\n\n> Hello World`,
|
|
||||||
date: "2024-01-01",
|
|
||||||
priority: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Execute entries
|
|
||||||
await newEntryExecution(1, eId2, { text: "Some execution txt" });
|
|
||||||
await newEntryExecution(2, eId3, { text: "More execution txt" });
|
|
||||||
|
|
||||||
return { eId1, eId2, eId3 };
|
|
||||||
}
|
|
||||||
|
|
||||||
test("create entry", async () => {
|
test("create entry", async () => {
|
||||||
const eId = await newEntry(1, {
|
const eId = await newEntry(1, {
|
||||||
patient_id: 1,
|
patient_id: 1,
|
||||||
|
@ -205,6 +163,46 @@ test("create entry execution (wrong old xid)", async () => {
|
||||||
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" }, x1 + 1)).rejects.toThrowError(new ErrorConflict("old execution id does not match"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function insertTestEntries() {
|
||||||
|
// Create some entries
|
||||||
|
const eId1 = await newEntry(1, {
|
||||||
|
patient_id: 1,
|
||||||
|
version: TEST_VERSION,
|
||||||
|
});
|
||||||
|
const eId2 = await newEntry(1, {
|
||||||
|
patient_id: 2,
|
||||||
|
version: {
|
||||||
|
text: "Carrot cake jelly-o bonbon toffee chocolate.",
|
||||||
|
date: "2024-01-05",
|
||||||
|
priority: false,
|
||||||
|
category_id: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const eId3 = await newEntry(1, {
|
||||||
|
patient_id: 1,
|
||||||
|
version: {
|
||||||
|
text: "Cheesecake danish donut oat cake caramels.",
|
||||||
|
date: "2024-01-06",
|
||||||
|
priority: false,
|
||||||
|
category_id: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update an entry
|
||||||
|
await newEntryVersion(2, eId1, {
|
||||||
|
category_id: 3,
|
||||||
|
text: `${TEST_VERSION.text}\n\n> Hello World`,
|
||||||
|
date: "2024-01-01",
|
||||||
|
priority: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Execute entries
|
||||||
|
await newEntryExecution(1, eId2, { text: "Some execution txt" });
|
||||||
|
await newEntryExecution(2, eId3, { text: "More execution txt" });
|
||||||
|
|
||||||
|
return { eId1, eId2, eId3 };
|
||||||
|
}
|
||||||
|
|
||||||
test("get entries", async () => {
|
test("get entries", async () => {
|
||||||
const { eId1, eId2, eId3 } = await insertTestEntries();
|
const { eId1, eId2, eId3 } = await insertTestEntries();
|
||||||
const entries = await getEntries({}, {});
|
const entries = await getEntries({}, {});
|
||||||
|
@ -292,15 +290,5 @@ 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(2);
|
||||||
});
|
|
||||||
|
|
||||||
test("get patient n entries", async () => {
|
|
||||||
await insertTestEntries();
|
|
||||||
expect(await getPatientNEntries(1)).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("get category n entries", async () => {
|
|
||||||
await insertTestEntries();
|
|
||||||
expect(await getCategoryNEntries(3)).toBe(1);
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -64,7 +64,9 @@ test("delete patient (restricted)", async () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(async () => deletePatient(pId)).rejects.toThrowError();
|
expect(async () => deletePatient(pId)).rejects.toThrowError(
|
||||||
|
"cannot delete patient with entries",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("hide patient", async () => {
|
test("hide patient", async () => {
|
||||||
|
|
|
@ -1,20 +1,42 @@
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
import type { RoomDetail } from "$lib/shared/model";
|
import type { Room, Station } from "$lib/shared/model";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteRoom,
|
deleteStation,
|
||||||
getRoom,
|
getRoom,
|
||||||
getRoomNPatients,
|
|
||||||
getRooms,
|
getRooms,
|
||||||
getStation,
|
getStation,
|
||||||
hideRoom,
|
getStations,
|
||||||
newRoom,
|
newRoom,
|
||||||
|
newStation,
|
||||||
updateStation,
|
updateStation,
|
||||||
} from "$lib/server/query";
|
} from "$lib/server/query";
|
||||||
import {
|
import { S1, S2 } from "$tests/helpers/testdata";
|
||||||
ROOMS, S1,
|
|
||||||
} from "$tests/helpers/testdata";
|
test("create station", async () => {
|
||||||
|
const sId = await newStation({ name: "S3" });
|
||||||
|
const station = await getStation(sId);
|
||||||
|
expect(station).toStrictEqual({ id: sId, name: "S3" } satisfies Station);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("update station", async () => {
|
||||||
|
const name = "HelloStation";
|
||||||
|
await updateStation(S1.id, { name });
|
||||||
|
const station = await getStation(S1.id);
|
||||||
|
expect(station.id).toBe(S1.id);
|
||||||
|
expect(station.name).toBe(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("delete station", async () => {
|
||||||
|
await deleteStation(S1.id);
|
||||||
|
expect(async () => getStation(S1.id)).rejects.toThrowError("No Station found");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("get stations", async () => {
|
||||||
|
const stations = await getStations();
|
||||||
|
expect(stations).toStrictEqual([S1, S2]);
|
||||||
|
});
|
||||||
|
|
||||||
test("create room", async () => {
|
test("create room", async () => {
|
||||||
const rId = await newRoom({ name: "A1", station_id: 1 });
|
const rId = await newRoom({ name: "A1", station_id: 1 });
|
||||||
|
@ -23,8 +45,7 @@ test("create room", async () => {
|
||||||
id: rId,
|
id: rId,
|
||||||
name: "A1",
|
name: "A1",
|
||||||
station: S1,
|
station: S1,
|
||||||
hidden: false,
|
} satisfies Room);
|
||||||
} satisfies RoomDetail);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("update room", async () => {
|
test("update room", async () => {
|
||||||
|
@ -36,26 +57,27 @@ test("update room", async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("delete room", async () => {
|
test("delete room", async () => {
|
||||||
await deleteRoom(ROOMS[3].id);
|
await deleteStation(S1.id);
|
||||||
expect(async () => getRoom(ROOMS[3].id)).rejects.toThrowError("No Room found");
|
expect(async () => getStation(S1.id)).rejects.toThrowError("No Station found");
|
||||||
});
|
|
||||||
|
|
||||||
test("hide room", async () => {
|
|
||||||
await hideRoom(ROOMS[0].id, true);
|
|
||||||
const rs1 = await getRooms();
|
|
||||||
const exp = [...ROOMS];
|
|
||||||
exp.splice(0, 1);
|
|
||||||
expect(rs1).toStrictEqual(exp);
|
|
||||||
await hideRoom(ROOMS[0].id, false);
|
|
||||||
const rs2 = await getRooms();
|
|
||||||
expect(rs2).toStrictEqual(ROOMS);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("get rooms", async () => {
|
test("get rooms", async () => {
|
||||||
const rooms = await getRooms();
|
const rooms = await getRooms();
|
||||||
expect(rooms).toStrictEqual(ROOMS);
|
expect(rooms).toStrictEqual([
|
||||||
});
|
{
|
||||||
|
id: 1,
|
||||||
test("get room n patients", async () => {
|
name: "R1.1",
|
||||||
expect(await getRoomNPatients(ROOMS[0].id)).toBe(1);
|
station: S1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "R1.2",
|
||||||
|
station: S1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "R2.1",
|
||||||
|
station: S2,
|
||||||
|
},
|
||||||
|
] satisfies Room[]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,129 +0,0 @@
|
||||||
import { expect, test } from "vitest";
|
|
||||||
|
|
||||||
import {
|
|
||||||
deleteSavedFilter,
|
|
||||||
getAllFilters,
|
|
||||||
getDefaultFilter, getSavedFilters, newSavedFilter, updateSavedFilter,
|
|
||||||
} from "$lib/server/query/savedFilter";
|
|
||||||
|
|
||||||
const VIEW = "plan";
|
|
||||||
|
|
||||||
async function createFilters() {
|
|
||||||
const fA1 = await newSavedFilter({
|
|
||||||
name: "Todo",
|
|
||||||
query: "filter[done]=false",
|
|
||||||
view: VIEW,
|
|
||||||
}, 1);
|
|
||||||
const fA2 = await newSavedFilter({
|
|
||||||
name: "Done",
|
|
||||||
query: "filter[done]=true",
|
|
||||||
view: VIEW,
|
|
||||||
}, 1);
|
|
||||||
const fADefault = await newSavedFilter({
|
|
||||||
name: "Default",
|
|
||||||
query: "filter[done]=true&filter[station][0][id]=1&filter[station][0][name]=S1",
|
|
||||||
view: VIEW,
|
|
||||||
}, 1);
|
|
||||||
const fADefaultP = await newSavedFilter({
|
|
||||||
name: "Default",
|
|
||||||
query: "filter[station][0][id]=1&filter[station][0][name]=S1",
|
|
||||||
view: "patients",
|
|
||||||
}, 1);
|
|
||||||
const fAOther = await newSavedFilter({
|
|
||||||
name: "Done",
|
|
||||||
query: "filter[done]=true",
|
|
||||||
view: "other",
|
|
||||||
}, 1);
|
|
||||||
const fB1 = await newSavedFilter({
|
|
||||||
name: "Prio",
|
|
||||||
query: "filter[priority]=true",
|
|
||||||
view: VIEW,
|
|
||||||
}, 2);
|
|
||||||
return {
|
|
||||||
fA1, fA2, fADefault, fB1, fADefaultP, fAOther,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const U1_FILTERS = (ids: Awaited<ReturnType<typeof createFilters>>) => [
|
|
||||||
// Default filter must be ordered on top
|
|
||||||
{
|
|
||||||
id: ids.fADefault,
|
|
||||||
name: "Default",
|
|
||||||
query: "filter[done]=true&filter[station][0][id]=1&filter[station][0][name]=S1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: ids.fA1,
|
|
||||||
name: "Todo",
|
|
||||||
query: "filter[done]=false",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: ids.fA2,
|
|
||||||
name: "Done",
|
|
||||||
query: "filter[done]=true",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
test("get filters", async () => {
|
|
||||||
const ids = await createFilters();
|
|
||||||
|
|
||||||
const u1Filters = await getSavedFilters(1, VIEW);
|
|
||||||
expect(u1Filters).toStrictEqual(U1_FILTERS(ids));
|
|
||||||
|
|
||||||
const u2Filters = await getSavedFilters(2, VIEW);
|
|
||||||
expect(u2Filters).toStrictEqual([
|
|
||||||
{
|
|
||||||
id: ids.fB1,
|
|
||||||
name: "Prio",
|
|
||||||
query: "filter[priority]=true",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("get default filter", async () => {
|
|
||||||
const ids = await createFilters();
|
|
||||||
|
|
||||||
const filter = await getDefaultFilter(1, VIEW);
|
|
||||||
expect(filter).toStrictEqual(U1_FILTERS(ids)[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("update filter", async () => {
|
|
||||||
const q = "filter[done]=true";
|
|
||||||
const ids = await createFilters();
|
|
||||||
await updateSavedFilter(ids.fADefault, q, 1);
|
|
||||||
|
|
||||||
const filter = await getDefaultFilter(1, VIEW);
|
|
||||||
expect(filter?.query).toBe(q);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("update filter not found", async () => {
|
|
||||||
expect(updateSavedFilter(1, "Hello World", 1)).rejects.toThrowError();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("delete filter", async () => {
|
|
||||||
const ids = await createFilters();
|
|
||||||
await deleteSavedFilter(ids.fADefault, 1);
|
|
||||||
const filter = await getDefaultFilter(1, VIEW);
|
|
||||||
expect(filter).toBe(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("get all filters", async () => {
|
|
||||||
const ids = await createFilters();
|
|
||||||
const filters = await getAllFilters(1);
|
|
||||||
expect(filters).toStrictEqual({
|
|
||||||
other: [
|
|
||||||
{
|
|
||||||
id: ids.fAOther,
|
|
||||||
name: "Done",
|
|
||||||
query: "filter[done]=true",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
patients: [
|
|
||||||
{
|
|
||||||
id: ids.fADefaultP,
|
|
||||||
name: "Default",
|
|
||||||
query: "filter[station][0][id]=1&filter[station][0][name]=S1",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
plan: U1_FILTERS(ids),
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { expect, test } from "vitest";
|
|
||||||
|
|
||||||
import type { StationDetail } from "$lib/shared/model";
|
|
||||||
|
|
||||||
import {
|
|
||||||
deleteStation, getStation, getStationNRooms, getStations, hideStation, newStation, updateStation,
|
|
||||||
} from "$lib/server/query";
|
|
||||||
import {
|
|
||||||
STATIONS, S1, S2, S3,
|
|
||||||
} from "$tests/helpers/testdata";
|
|
||||||
|
|
||||||
test("create station", async () => {
|
|
||||||
const sId = await newStation({ name: "S3" });
|
|
||||||
const station = await getStation(sId);
|
|
||||||
expect(station).toStrictEqual({ id: sId, name: "S3", hidden: false } satisfies StationDetail);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("update station", async () => {
|
|
||||||
const name = "HelloStation";
|
|
||||||
await updateStation(S1.id, { name });
|
|
||||||
const station = await getStation(S1.id);
|
|
||||||
expect(station.id).toBe(S1.id);
|
|
||||||
expect(station.name).toBe(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("delete station", async () => {
|
|
||||||
await deleteStation(S3.id);
|
|
||||||
expect(async () => getStation(S3.id)).rejects.toThrowError("No Station found");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("hide station", async () => {
|
|
||||||
await hideStation(S1.id, true);
|
|
||||||
const cs1 = await getStations();
|
|
||||||
expect(cs1).toStrictEqual([S2, S3]);
|
|
||||||
await hideStation(S1.id, false);
|
|
||||||
const cs2 = await getStations();
|
|
||||||
expect(cs2).toStrictEqual(STATIONS);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("get stations", async () => {
|
|
||||||
const stations = await getStations();
|
|
||||||
expect(stations).toStrictEqual(STATIONS);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("get station n rooms", async () => {
|
|
||||||
expect(await getStationNRooms(1)).toBe(2);
|
|
||||||
});
|
|
|
@ -1,77 +1,9 @@
|
||||||
import { exec } from "child_process";
|
|
||||||
import { promisify } from "util";
|
|
||||||
|
|
||||||
import { sveltekit } from "@sveltejs/kit/vite";
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
import { createViteLicensePlugin } from "rollup-license-plugin";
|
|
||||||
import { defineConfig } from "vitest/config";
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
// Get current tag/commit and last commit date from git
|
|
||||||
const pexec = promisify(exec);
|
|
||||||
let [version, lastmod] = (
|
|
||||||
await Promise.allSettled([
|
|
||||||
pexec("git describe --tags || git rev-parse --short HEAD"),
|
|
||||||
pexec('git log -1 --format=%cd --date=format:"%Y-%m-%d %H:%M"'),
|
|
||||||
])
|
|
||||||
).map((v) => {
|
|
||||||
if (v.status !== "fulfilled") throw Error("could not get version info from git");
|
|
||||||
return JSON.stringify(v.value.stdout.trim());
|
|
||||||
});
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [sveltekit()],
|
||||||
sveltekit(),
|
|
||||||
createViteLicensePlugin({
|
|
||||||
additionalFiles: {
|
|
||||||
"oss-licenses.html": (packages) => {
|
|
||||||
let res = `<html>
|
|
||||||
<head>
|
|
||||||
<title>Visitenbuch - Lizenzen</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Open-Source-Lizenzen</h1>
|
|
||||||
<a href="./oss-licenses.json">JSON-formatted license list</a>
|
|
||||||
`;
|
|
||||||
for (const p of packages) {
|
|
||||||
// @ts-expect-error repo not present in type definition
|
|
||||||
let rp = p.repository;
|
|
||||||
// @ts-expect-error author not present in type definition
|
|
||||||
let aut = p.author;
|
|
||||||
if (typeof aut === "object") {
|
|
||||||
aut = aut.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
let repoUrl = null;
|
|
||||||
if (typeof rp === "string") {
|
|
||||||
if (rp.startsWith("http")) repoUrl = rp;
|
|
||||||
else if (rp.startsWith("git+http")) repoUrl = rp.substring(4);
|
|
||||||
else if (rp.startsWith("git://")) repoUrl = "https://" + rp.substring(6);
|
|
||||||
else if (rp.startsWith("github:")) repoUrl = "https://github.com/" + rp.substring(7);
|
|
||||||
else if (rp.match(/^[\w-]+\/[\w-]+$/)) repoUrl = "https://github.com/" + rp;
|
|
||||||
}
|
|
||||||
|
|
||||||
res += `<div class="package">\n`;
|
|
||||||
res += `<h3><a href="https://www.npmjs.com/package/${p.name}" target="_blank" rel="noopener noreferrer">${p.name}</a></h3>\n`;
|
|
||||||
res += `<table>\n`;
|
|
||||||
res += `<tr><td>Version:</td><td>${p.version}</td></tr>\n`;
|
|
||||||
if (aut) res += `<tr><td>Author:</td><td>${aut}</td></tr>\n`;
|
|
||||||
res += `<tr><td>License:</td><td>${p.license}</td></tr>\n`;
|
|
||||||
if (repoUrl) res += `<tr><td>Repository:</td><td><a href="${repoUrl}" target="_blank" rel="noopener noreferrer">${repoUrl}</a></td></tr>\n`;
|
|
||||||
else if (rp) res += `<tr><td>Repository:</td><td>${rp}</td></tr>\n`;
|
|
||||||
res += `</table>\n`;
|
|
||||||
res += "</div>";
|
|
||||||
}
|
|
||||||
|
|
||||||
res += "</body>\n</html>\n";
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
test: {
|
test: {
|
||||||
include: ["src/**/*.{test,spec}.{js,ts}"],
|
include: ["src/**/*.{test,spec}.{js,ts}"],
|
||||||
},
|
},
|
||||||
define: {
|
|
||||||
__VERSION__: version,
|
|
||||||
__LASTMOD__: lastmod,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue