Compare commits

...

170 Commits

Author SHA1 Message Date
Matt Kilgore
214b16a26e chore: update go dependencies 2024-09-14 10:24:07 -04:00
Katos
405d0c7487 Merge pull request #212 from tonyaellie/make-ItemViewTable-headers-customisable
make ItemViewTable headers customisable
2024-09-13 19:53:21 +01:00
Katos
6800c2112e Merge pull request #221 from Gabrinator/update-docs
Update Documentation: Quick Start
2024-09-13 19:51:20 +01:00
Gabrinator
5fc7b3e25b docs: update quick-stard.md 2024-09-13 12:49:02 -04:00
Gabrinator
88dc943b6b docs: added Configure Homebox Page 2024-09-13 12:48:18 -04:00
Gabrinator
f8482b1c64 docs: fix typo in index.md 2024-09-13 12:41:37 -04:00
Gabrinator
a6e49295e0 docs: update navigation 2024-09-13 11:37:24 -04:00
Gabrinator
8ef1b8b6ce docs: add installation page 2024-09-13 11:36:18 -04:00
Weblate
404791a344 Translated using Weblate (French)
Currently translated at 68.0% (96 of 141 strings)

Translated using Weblate (French)

Currently translated at 68.0% (96 of 141 strings)

Co-authored-by: Jean-Philippe Baril <translate.sysadminsmedia.com@alias.trebaxis.net>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translation: Homebox/Frontend
2024-09-12 03:26:35 +00:00
Tonya
073aade67f feat: show item is archived in more places (#210) 2024-09-11 10:48:13 -04:00
Weblate
784cc409d4 Translated using Weblate (German)
Currently translated at 79.4% (112 of 141 strings)

Translated using Weblate (German)

Currently translated at 79.4% (112 of 141 strings)

Translated using Weblate (German)

Currently translated at 79.4% (112 of 141 strings)

Translated using Weblate (German)

Currently translated at 79.4% (112 of 141 strings)

Co-authored-by: Mats <sysadminsmedia@mats-bueser.de>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Nic <nicmeier1@gmx.net>
Co-authored-by: Sascha Brockel <gsydaydreamer@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translation: Homebox/Frontend
2024-09-11 12:48:35 +00:00
Matt Kilgore
9e3f82fbac fix: action should run at midnight for nightly builds 2024-09-10 09:44:28 -04:00
Weblate
19c6d4dec5 Translated using Weblate (Chinese (Simplified))
Currently translated at 65.2% (92 of 141 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 65.2% (92 of 141 strings)

Translated using Weblate (German)

Currently translated at 73.7% (104 of 141 strings)

Translated using Weblate (German)

Currently translated at 73.7% (104 of 141 strings)

Translated using Weblate (German)

Currently translated at 73.7% (104 of 141 strings)

Translated using Weblate (English)

Currently translated at 100.0% (141 of 141 strings)

Co-authored-by: Matthew Kilgore <matthew@kilgore.dev>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Sascha Brockel <gsydaydreamer@gmail.com>
Co-authored-by: dongzh <whitefivefiveopen@gmail.com>
Co-authored-by: hirnchirurg <wilhelm.humerez@posteo.de>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/en/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-10 02:48:35 +00:00
tonya
b18f0c790b feat: make ItemViewTable headers customisable 2024-09-09 19:38:58 +00:00
Tonya
fb62f51958 feat: refresh search on include archived change (#208) 2024-09-08 20:15:52 -04:00
Tonya
dafc6aa13f update links (#206)
* chore: update links

* feat: update social link
2024-09-08 20:11:50 -04:00
Matt Kilgore
93f13b1e80 chore: update go dependencies 2024-09-08 15:25:39 -04:00
Matt Kilgore
5de649d85f feat: update PNPM dependencies 2024-09-08 14:59:25 -04:00
Matt Kilgore
b37cf24f09 feat: user selectable language 2024-09-08 14:19:01 -04:00
Matt Kilgore
cf2edc8d34 fix: Use region specific chinese (instead of simplified vs traditional) 2024-09-08 13:36:01 -04:00
Matt Kilgore
f113de180b docs: add demo link to doc site 2024-09-08 13:17:48 -04:00
Edward Shen
adb4b52752 Fix Registration Disabled button in login page (#203) 2024-09-08 13:13:15 -04:00
Weblate
baf8912dda Added translation using Weblate (Portuguese (Brazil))
Co-authored-by: Daniel Zanardi de Souza <zz.uploader@gmail.com>
2024-09-07 22:56:18 +00:00
Matt Kilgore
489deda6a8 Update README.md 2024-09-07 15:45:13 -04:00
Matt Kilgore
2fee607327 feat: allow the hiding of the header (#202) 2024-09-07 15:09:40 -04:00
Matt Kilgore
c428a22b5b docs: add screenshot of home screen 2024-09-07 14:31:35 -04:00
Matt Kilgore
42c01adb98 feat: translate tools page 2024-09-07 13:46:24 -04:00
Matt Kilgore
209bb2932c refactor: cleaned up translation matching 2024-09-07 13:01:02 -04:00
Matt Kilgore
ec9cdb391a Merge remote-tracking branch 'origin/main'
# Conflicts:
#	frontend/plugins/i18n.ts
2024-09-07 12:41:37 -04:00
Matt Kilgore
a6aafeb374 fix: compare all browser languages with available offerings. 2024-09-07 12:41:26 -04:00
Weblate
ffb538ef21 Translated using Weblate (Hungarian)
Currently translated at 53.3% (48 of 90 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (90 of 90 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Xavier Clotet <x.clotetfons@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ca/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-07 07:48:36 +00:00
Tonya
15925de2f0 feat: add eslint-plugin-tailwindcss (#199) 2024-09-06 10:49:28 -04:00
Weblate
25d72044e9 Added translation using Weblate (Hungarian)
Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
2024-09-06 11:25:56 +00:00
Weblate
6b598383d3 Translated using Weblate (Polish)
Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Catalan)

Currently translated at 83.3% (75 of 90 strings)

Translated using Weblate (Catalan)

Currently translated at 83.3% (75 of 90 strings)

Co-authored-by: Bart <bartosz.domanski97@gmail.com>
Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Xavier Clotet <x.clotetfons@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ca/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pl/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-06 07:32:16 +00:00
Tonya
25c76522d6 Merge pull request #185
* feat: changeable items per table page

* Merge branch 'main' into main
2024-09-05 19:57:12 -04:00
Tonya
c0e2aa5c62 Merge pull request #197
* feat: add search to multi select and improve behaviour when multiple …
2024-09-05 19:52:26 -04:00
Weblate
d0b9f742ae Translated using Weblate (Polish)
Currently translated at 88.8% (80 of 90 strings)

Translated using Weblate (Polish)

Currently translated at 88.8% (80 of 90 strings)

Co-authored-by: Bart <bartosz.domanski97@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pl/
Translation: Homebox/Frontend
2024-09-05 20:53:24 +00:00
Weblate
80d56829c5 Translated using Weblate (German)
Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Polish)

Currently translated at 81.1% (73 of 90 strings)

Translated using Weblate (Polish)

Currently translated at 81.1% (73 of 90 strings)

Translated using Weblate (Catalan)

Currently translated at 48.8% (44 of 90 strings)

Co-authored-by: Bart <bartosz.domanski97@gmail.com>
Co-authored-by: Mats <sysadminsmedia@mats-bueser.de>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Xavier Clotet <x.clotetfons@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ca/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pl/
Translation: Homebox/Frontend
2024-09-05 20:52:03 +00:00
Weblate
0946310f60 Added translation using Weblate (Polish)
Co-authored-by: Bart <bartosz.domanski97@gmail.com>
2024-09-05 20:32:16 +00:00
Matt Kilgore
7c855cf55d fix: regional languages not matching correctly 2024-09-05 15:48:00 -04:00
Tonya
0ab95fb670 feat: compare filter values on a unique field instead of by reference for finding unselected (#195) 2024-09-05 15:41:29 -04:00
Tonya
1e81b4bab4 feat: improve loading state for creation and fix types for adding image (#196) 2024-09-05 15:40:57 -04:00
Tonya
67c50068d9 fix: styles on home page (#193) 2024-09-05 14:29:33 -04:00
Weblate
c3628e36f7 Translated using Weblate (French)
Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (French)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (French)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Catalan)

Currently translated at 43.3% (39 of 90 strings)

Translated using Weblate (Catalan)

Currently translated at 43.3% (39 of 90 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (German)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (German)

Currently translated at 100.0% (90 of 90 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: Dest.Com <azevedo-a@hotmail.fr>
Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: Maxklos <herzognikolaus@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Stair <nrjava06@gmail.com>
Co-authored-by: Xavier Clotet <x.clotetfons@gmail.com>
Co-authored-by: thehijacker <thehijacker@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ca/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/sl/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-05 14:20:41 +00:00
Weblate
526799c6da Added translation using Weblate (Catalan)
Co-authored-by: Xavier Clotet <x.clotetfons@gmail.com>
2024-09-05 12:35:43 +00:00
Weblate
4ef7529533 Translated using Weblate (Slovenian)
Currently translated at 37.7% (34 of 90 strings)

Translated using Weblate (Slovenian)

Currently translated at 37.7% (34 of 90 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (90 of 90 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Slydite4 <39199098+Slydite4@users.noreply.github.com>
Co-authored-by: thehijacker <thehijacker@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/es/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/sl/
Translation: Homebox/Frontend
2024-09-05 04:36:37 +00:00
Weblate
b06d670dff Added translation using Weblate (Slovenian)
Co-authored-by: thehijacker <thehijacker@gmail.com>
2024-09-05 04:29:38 +00:00
Weblate
02c0453ff3 Translated using Weblate (French)
Currently translated at 94.4% (85 of 90 strings)

Co-authored-by: Jean-Philippe Baril <weblate.org@alias.trebaxis.net>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translation: Homebox/Frontend
2024-09-04 18:48:34 +00:00
Weblate
09358aa5b2 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Dutch)

Currently translated at 96.6% (87 of 90 strings)

Co-authored-by: Andreas Olsson <basen82@icloud.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: SKNTim <timmy444074@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/sv/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hant/
Translation: Homebox/Frontend
2024-09-03 01:00:07 +00:00
Weblate
dbe77ea19d Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (90 of 90 strings)

Co-authored-by: Jackxwb <xwb9606@163.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hant/
Translation: Homebox/Frontend
2024-09-02 02:01:14 +00:00
Weblate
85e5c7e8e7 Translated using Weblate (Chinese (Simplified))
Currently translated at 97.7% (88 of 90 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 97.7% (88 of 90 strings)

Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-01 16:38:10 +00:00
Weblate
3c273b370d Translated using Weblate (Chinese (Simplified))
Currently translated at 96.6% (87 of 90 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 96.6% (87 of 90 strings)

Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-01 16:37:09 +00:00
Weblate
343e56b440 Translated using Weblate (Chinese (Simplified))
Currently translated at 95.5% (86 of 90 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 95.5% (86 of 90 strings)

Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-01 16:36:35 +00:00
Weblate
3a949aee5a Translated using Weblate (Chinese (Simplified))
Currently translated at 70.0% (63 of 90 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 70.0% (63 of 90 strings)

Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-01 16:26:19 +00:00
Weblate
1601e52c9c Translated using Weblate (Chinese (Simplified))
Currently translated at 65.5% (59 of 90 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 65.5% (59 of 90 strings)

Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-01 16:21:10 +00:00
Weblate
760cc8e35c Translated using Weblate (Chinese (Simplified))
Currently translated at 46.6% (42 of 90 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 46.6% (42 of 90 strings)

Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-01 16:13:52 +00:00
Weblate
6051e1fb8b Translated using Weblate (Chinese (Simplified))
Currently translated at 45.5% (41 of 90 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 45.5% (41 of 90 strings)

Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-01 16:13:02 +00:00
Weblate
7b146947df Translated using Weblate (Chinese (Simplified))
Currently translated at 36.6% (33 of 90 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 36.6% (33 of 90 strings)

Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-01 16:08:11 +00:00
Weblate
5497a10f9f Translated using Weblate (Chinese (Simplified))
Currently translated at 25.5% (23 of 90 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 25.5% (23 of 90 strings)

Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-01 16:01:32 +00:00
Weblate
3e6f4b3657 Added translation using Weblate (Chinese (Simplified))
Co-authored-by: Jackxwb <xwb9606@163.com>
2024-09-01 15:45:59 +00:00
Weblate
7baf58ad61 Translated using Weblate (Turkish)
Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (90 of 90 strings)

Co-authored-by: Hakan Bildir <divxtr@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/tr/
Translation: Homebox/Frontend
2024-09-01 10:42:51 +00:00
Matt Kilgore
d72437d18c Update config.mts 2024-08-31 22:38:55 -04:00
Weblate
ea57981953 Translated using Weblate (Turkish)
Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (90 of 90 strings)

Co-authored-by: Hakan Bildir <divxtr@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/tr/
Translation: Homebox/Frontend
2024-08-31 19:57:38 +00:00
Weblate
c2d0cce02d Translated using Weblate (Turkish)
Currently translated at 71.1% (64 of 90 strings)

Translated using Weblate (Turkish)

Currently translated at 71.1% (64 of 90 strings)

Translated using Weblate (Turkish)

Currently translated at 71.1% (64 of 90 strings)

Co-authored-by: Hakan Bildir <divxtr@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Salad <selcuk.erbek@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/tr/
Translation: Homebox/Frontend
2024-08-31 19:51:29 +00:00
Weblate
9f7a119e95 Translated using Weblate (Russian)
Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Turkish)

Currently translated at 58.8% (53 of 90 strings)

Co-authored-by: Fedor M <k930bx@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ru/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/tr/
Translation: Homebox/Frontend
2024-08-31 19:48:29 +00:00
Weblate
0dacc97e99 Translated using Weblate (French)
Currently translated at 94.4% (85 of 90 strings)

Translated using Weblate (French)

Currently translated at 94.4% (85 of 90 strings)

Co-authored-by: Jean-Philippe Baril <weblate.org@alias.trebaxis.net>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translation: Homebox/Frontend
2024-08-30 19:48:35 +00:00
Weblate
52a44da56b Translated using Weblate (French)
Currently translated at 91.1% (82 of 90 strings)

Translated using Weblate (French)

Currently translated at 91.1% (82 of 90 strings)

Co-authored-by: Jean-Philippe Baril <weblate.org@alias.trebaxis.net>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translation: Homebox/Frontend
2024-08-29 19:28:00 +00:00
Weblate
7114f262c2 Translated using Weblate (French)
Currently translated at 90.0% (81 of 90 strings)

Translated using Weblate (French)

Currently translated at 90.0% (81 of 90 strings)

Co-authored-by: Jean-Philippe Baril <weblate.org@alias.trebaxis.net>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translation: Homebox/Frontend
2024-08-29 19:13:29 +00:00
Cosmo
7647ea96d1 added the ability to add a photo in the item creation modal (#173)
* added the ability to add a photo in the item creation modal

* fixed problem with create button being hidden on tiny screens

* fix: ui, translations

---------

Co-authored-by: Matt Kilgore <matthew@kilgore.dev>
2024-08-28 20:26:45 -04:00
Oliver Larsson
593da25cdb fix: CSV export not including item notes (#180) 2024-08-28 19:44:37 -04:00
Weblate
f22bce7ccb Translated using Weblate (Russian)
Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (German)

Currently translated at 100.0% (89 of 89 strings)

Co-authored-by: Dominik <account@kowanda.net>
Co-authored-by: Fedor M <k930bx@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ru/
Translation: Homebox/Frontend
2024-08-28 05:09:49 +00:00
Romulo Gatto
1688773bba adding email validator (#178) 2024-08-25 18:46:55 -04:00
Weblate
b56b5d2400 Translated using Weblate (Russian)
Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Turkish)

Currently translated at 57.3% (51 of 89 strings)

Co-authored-by: Fedor M <k930bx@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ru/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/tr/
Translation: Homebox/Frontend
2024-08-25 18:48:35 +00:00
Weblate
33ee208071 Translated using Weblate (Russian)
Currently translated at 33.7% (30 of 89 strings)

Translated using Weblate (Russian)

Currently translated at 33.7% (30 of 89 strings)

Co-authored-by: Fedor M <k930bx@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ru/
Translation: Homebox/Frontend
2024-08-24 18:39:26 +00:00
Weblate
fe880cc2c7 Translated using Weblate (Russian)
Currently translated at 25.8% (23 of 89 strings)

Translated using Weblate (Russian)

Currently translated at 25.8% (23 of 89 strings)

Co-authored-by: Fedor M <k930bx@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ru/
Translation: Homebox/Frontend
2024-08-24 18:37:51 +00:00
Weblate
cffe57b74e Added translation using Weblate (Russian)
Co-authored-by: Fedor M <k930bx@gmail.com>
2024-08-24 18:25:04 +00:00
Weblate
66882d6fd9 Translated using Weblate (Spanish)
Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (89 of 89 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Slydite4 <39199098+Slydite4@users.noreply.github.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/es/
Translation: Homebox/Frontend
2024-08-22 18:36:20 +00:00
Weblate
050f22f051 Translated using Weblate (Spanish)
Currently translated at 60.6% (54 of 89 strings)

Translated using Weblate (Spanish)

Currently translated at 60.6% (54 of 89 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Slydite4 <39199098+Slydite4@users.noreply.github.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/es/
Translation: Homebox/Frontend
2024-08-22 07:24:54 +00:00
Weblate
7891af3a9a Translated using Weblate (Spanish)
Currently translated at 58.4% (52 of 89 strings)

Translated using Weblate (Spanish)

Currently translated at 58.4% (52 of 89 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Slydite4 <39199098+Slydite4@users.noreply.github.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/es/
Translation: Homebox/Frontend
2024-08-22 07:23:48 +00:00
Weblate
40cbccf50a Translated using Weblate (Spanish)
Currently translated at 56.1% (50 of 89 strings)

Translated using Weblate (Spanish)

Currently translated at 56.1% (50 of 89 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Slydite4 <39199098+Slydite4@users.noreply.github.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/es/
Translation: Homebox/Frontend
2024-08-22 07:23:06 +00:00
Weblate
0348da362c Translated using Weblate (Spanish)
Currently translated at 51.6% (46 of 89 strings)

Translated using Weblate (Spanish)

Currently translated at 51.6% (46 of 89 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Slydite4 <39199098+Slydite4@users.noreply.github.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/es/
Translation: Homebox/Frontend
2024-08-22 07:21:48 +00:00
Weblate
f0a3780f3a Added translation using Weblate (Spanish)
Co-authored-by: Slydite4 <39199098+Slydite4@users.noreply.github.com>
2024-08-22 07:00:49 +00:00
Weblate
39163f3cfc Translated using Weblate (French)
Currently translated at 88.7% (79 of 89 strings)

Translated using Weblate (French)

Currently translated at 88.7% (79 of 89 strings)

Co-authored-by: Julien <hydreliox@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translation: Homebox/Frontend
2024-08-16 06:40:28 +00:00
Weblate
43676ab407 Translated using Weblate (French)
Currently translated at 77.5% (69 of 89 strings)

Translated using Weblate (French)

Currently translated at 77.5% (69 of 89 strings)

Translated using Weblate (French)

Currently translated at 77.5% (69 of 89 strings)

Translated using Weblate (Dutch)

Currently translated at 96.6% (86 of 89 strings)

Co-authored-by: Julien <hydreliox@gmail.com>
Co-authored-by: Matthew Kilgore <matthew@kilgore.dev>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translation: Homebox/Frontend
2024-08-15 18:28:54 +00:00
Lukas
639f795b9a [LANGUAGE UPDATE] Frontend translations for Italian and German (#170)
* Update it.json

Translate all strings into italian and reorder alphabetically

* Update de.json

Translated all strings into German and sorted them alphabetically.

* Update it.json

Added missing commas

* Update it.json

Fix indentation and remove unnecessary curly bracket left behind during the translation

* Replace 'Edit', 'Create', and 'Other' with their Italian equivalents

* Replace 'Edit', 'Create', and 'Other' with their German equivalents
2024-08-14 09:08:12 -04:00
Weblate
fc95d2cab8 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Dutch)

Currently translated at 94.3% (84 of 89 strings)

Translated using Weblate (Dutch)

Currently translated at 94.3% (84 of 89 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: SKNTim <timmy444074@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hant/
Translation: Homebox/Frontend
2024-08-13 18:48:34 +00:00
Weblate
695b6d68e6 Added translation using Weblate (Chinese (Traditional))
Co-authored-by: SKNTim <timmy444074@gmail.com>
2024-08-13 02:40:59 +00:00
Weblate
b6c265098d Translated using Weblate (Swedish)
Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Dutch)

Currently translated at 91.0% (81 of 89 strings)

Translated using Weblate (Dutch)

Currently translated at 91.0% (81 of 89 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: Andreas Olsson <basen82@icloud.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/sv/
Translation: Homebox/Frontend
2024-08-12 15:05:56 +00:00
Weblate
2a80d348bd Translated using Weblate (Swedish)
Currently translated at 80.8% (72 of 89 strings)

Translated using Weblate (Turkish)

Currently translated at 47.1% (42 of 89 strings)

Translated using Weblate (Dutch)

Currently translated at 74.1% (66 of 89 strings)

Translated using Weblate (Dutch)

Currently translated at 74.1% (66 of 89 strings)

Translated using Weblate (Dutch)

Currently translated at 74.1% (66 of 89 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: Andreas Olsson <basen82@icloud.com>
Co-authored-by: Hakan Bildir <divxtr@gmail.com>
Co-authored-by: Matthew Kilgore <matthew@kilgore.dev>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/sv/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/tr/
Translation: Homebox/Frontend
2024-08-12 10:16:48 +00:00
Weblate
c6542de93d Translated using Weblate (Swedish)
Currently translated at 58.4% (52 of 89 strings)

Translated using Weblate (Turkish)

Currently translated at 39.3% (35 of 89 strings)

Translated using Weblate (Turkish)

Currently translated at 39.3% (35 of 89 strings)

Translated using Weblate (French)

Currently translated at 65.1% (58 of 89 strings)

Translated using Weblate (German)

Currently translated at 58.4% (52 of 89 strings)

Translated using Weblate (Italian)

Currently translated at 39.3% (35 of 89 strings)

Translated using Weblate (Italian)

Currently translated at 39.3% (35 of 89 strings)

Translated using Weblate (Dutch)

Currently translated at 61.7% (55 of 89 strings)

Translated using Weblate (Dutch)

Currently translated at 61.7% (55 of 89 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Matthew Kilgore <matthew@kilgore.dev>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/it/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/sv/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/tr/
Translation: Homebox/Frontend
2024-08-11 23:45:32 +00:00
Weblate
5928678564 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/
Translation: Homebox/Frontend
2024-08-11 23:44:45 +00:00
Matt Kilgore
fac52ca122 chore: alphabetize english JSON 2024-08-11 19:42:03 -04:00
Matt Kilgore
e9d270269f Merge remote-tracking branch 'origin/main' 2024-08-11 19:40:27 -04:00
Matt Kilgore
0a4c5fbb28 chore: translate items page 2024-08-11 19:40:13 -04:00
Weblate
2bfb0283d9 Translated using Weblate (Dutch)
Currently translated at 90.1% (55 of 61 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translation: Homebox/Frontend
2024-08-11 19:46:14 +00:00
Weblate
94e8aee36f Translated using Weblate (French)
Currently translated at 96.7% (59 of 61 strings)

Translated using Weblate (Dutch)

Currently translated at 85.2% (52 of 61 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: Chevdor <chevdor@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translation: Homebox/Frontend
2024-08-11 19:45:11 +00:00
Matthew Kilgore
8051956a2e Deleted translation using Weblate (English (Pirate)) 2024-08-11 18:51:00 +00:00
Matt Kilgore
c7020503be Merge remote-tracking branch 'origin/main' 2024-08-11 14:09:58 -04:00
Matt Kilgore
28edce96d9 chore: Finish profile page translations 2024-08-11 14:09:51 -04:00
Weblate
b4481fcc84 Translated using Weblate (German)
Currently translated at 100.0% (53 of 53 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (53 of 53 strings)

Co-authored-by: Andreas Olsson <basen82@icloud.com>
Co-authored-by: Philipp Walter <philippwalter21@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/sv/
Translation: Homebox/Frontend
2024-08-11 07:11:39 +00:00
Weblate
2be2bebb4e Translated using Weblate (Swedish)
Currently translated at 100.0% (53 of 53 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (53 of 53 strings)

Co-authored-by: Andreas Olsson <basen82@icloud.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/sv/
Translation: Homebox/Frontend
2024-08-10 05:47:28 +00:00
Weblate
d4bb8def62 Added translation using Weblate (Swedish)
Co-authored-by: Andreas Olsson <basen82@icloud.com>
2024-08-10 05:22:48 +00:00
Weblate
7442cb01b7 Translated using Weblate (French)
Currently translated at 96.2% (51 of 53 strings)

Translated using Weblate (English)

Currently translated at 100.0% (53 of 53 strings)

Co-authored-by: Matthew Kilgore <matthew@kilgore.dev>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/en/
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translation: Homebox/Frontend
2024-08-09 14:48:19 +00:00
Weblate
95ba8275e8 Translated using Weblate (Dutch)
Currently translated at 92.4% (49 of 53 strings)

Translated using Weblate (French)

Currently translated at 98.1% (52 of 53 strings)

Translated using Weblate (English (Pirate))

Currently translated at 13.3% (6 of 45 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: Matthew Kilgore <matthew@kilgore.dev>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: thhurt <th.hurtado+weblate@gmail.com>
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/en@pirate/
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translation: Homebox/Frontend
2024-08-08 14:45:24 +00:00
Matt Kilgore
2f4a0dd212 fix: #146 add version info to every page 2024-08-07 21:55:05 -04:00
Matt Kilgore
52a621e9ba fix: use the browser default language 2024-08-07 20:57:40 -04:00
Matt Kilgore
1f77fad829 fix: i18n ICU loading failure due to original auto-loading. 2024-08-07 20:26:40 -04:00
Weblate
8d93a3f56e Translated using Weblate (German)
Currently translated at 100.0% (35 of 35 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (35 of 35 strings)

Co-authored-by: Matthew Kilgore <matthew@kilgore.dev>
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/it/
Translation: Homebox/Frontend
2024-08-06 13:54:10 +00:00
Weblate
9c572e7ab2 Translated using Weblate (French)
Currently translated at 100.0% (35 of 35 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (35 of 35 strings)

Translated using Weblate (German)

Currently translated at 100.0% (35 of 35 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (35 of 35 strings)

Co-authored-by: Hakan Bildir <divxtr@gmail.com>
Co-authored-by: Jean-Philippe Baril <weblate.org@alias.trebaxis.net>
Co-authored-by: Lukas Hofer <naluso@protonmail.com>
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/it/
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/tr/
Translation: Homebox/Frontend
2024-08-05 13:04:00 +00:00
A3
1f15e74730 changed companyname and url to sysadminsmedia.com (#158) 2024-08-05 08:59:53 -04:00
Weblate
7570a04c02 Added translation using Weblate (Turkish)
Co-authored-by: Hakan Bildir <divxtr@gmail.com>
2024-08-05 07:56:36 +00:00
Weblate
fc2e89c448 Added translation using Weblate (French)
Co-authored-by: Jean-Philippe Baril <weblate.org@alias.trebaxis.net>
2024-08-05 07:56:25 +00:00
Weblate
be216ff7fe Translated using Weblate (German)
Currently translated at 40.0% (14 of 35 strings)

Co-authored-by: Benjamin Kahlau <nyhkkbjyek@roanapur.de>
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translation: Homebox/Frontend
2024-08-05 00:10:39 +00:00
Weblate
388208571b Added translation using Weblate (German)
Co-authored-by: Benjamin Kahlau <nyhkkbjyek@roanapur.de>
2024-08-04 22:34:52 +00:00
Weblate
1891903007 Translated using Weblate (English (Pirate))
Currently translated at 11.4% (4 of 35 strings)

Co-authored-by: Matthew Kilgore <matthew@kilgore.dev>
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/en@pirate/
Translation: Homebox/Frontend
2024-08-04 22:25:05 +00:00
Matt Kilgore
41a7e73ff4 Merge remote-tracking branch 'origin/main' 2024-08-04 17:36:11 -04:00
Matt Kilgore
81d9fb0700 chore: add translations to contributions sections 2024-08-04 17:36:02 -04:00
Weblate
76312d6eb6 Added translation using Weblate (Italian)
Co-authored-by: Lukas Hofer <naluso@protonmail.com>
2024-08-04 21:26:53 +00:00
Weblate
f31528c841 Added translation using Weblate (English (Pirate))
Co-authored-by: Matthew Kilgore <matthew@kilgore.dev>
2024-08-04 20:53:17 +00:00
Weblate
6d869fdece Translated using Weblate (Dutch)
Currently translated at 100.0% (35 of 35 strings)

Translated using Weblate (English)

Currently translated at 100.0% (35 of 35 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: Weblate Admin <admin@sysadminsmedia.com>
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/en/
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translation: Homebox/Frontend
2024-08-04 20:16:58 +00:00
Weblate
d0784a7773 Added translation using Weblate (Dutch)
Co-authored-by: Matthew Kilgore <matthew@kilgore.dev>
2024-08-04 19:27:09 +00:00
Weblate Admin
791f843bc8 Deleted translation using Weblate (Chinese (Simplified)) 2024-08-04 18:21:20 +00:00
Matt Kilgore
a0cdb231fd chore: translate components 2024-08-04 14:08:05 -04:00
Matt Kilgore
e0004842e6 fix: bad translation 2024-08-04 13:37:37 -04:00
Matt Kilgore
fdbfa0e76f refactor: better translation formatting 2024-08-04 13:11:27 -04:00
Matt Kilgore
005516013f chore: delete unneeded translation file 2024-08-04 12:56:12 -04:00
Weblate
9ec3dd4b16 Added translation using Weblate (Chinese (Simplified))
Translated using Weblate (English)

Currently translated at 100.0% (19 of 19 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Weblate Admin <admin@sysadminsmedia.com>
Translate-URL: http://translate.sysadminsmedia.com/projects/homebox/frontend/en/
Translation: Homebox/Frontend
2024-08-04 16:47:43 +00:00
Matt Kilgore
4dacf981a9 Merge branch 'refs/heads/mk/i18n'
# Conflicts:
#	frontend/pages/index.vue
2024-08-04 11:35:37 -04:00
Matt Kilgore
9f7b76b37d chore: minor tweak 2024-08-04 11:31:16 -04:00
Matt Kilgore
0d51558e74 chore: more translations 2024-08-04 11:26:07 -04:00
Matt Kilgore
236c257892 feat: adding initial i18n support (#155) 2024-08-04 11:02:29 -04:00
Matt Kilgore
3540ce4297 feat: adding initial i18n support 2024-08-03 23:04:26 -04:00
Rylie Pavlik
0bcb155756 Fixes to the Tools page (#154)
* Correct the description of the import feature on the tools page.

* Fix page title on Tools page.
2024-08-01 21:52:13 -04:00
Fuzzy
12219522ab Document search tips, cleanup documentation (#152)
* Update tips-tricks.md

Add instructions for search by ID

* Update get-started.md

Fixing typos and redundancies
2024-07-31 19:51:43 -04:00
Katos
13864997ab Merge pull request #149 from victorhooi/patch-1
Fix small typo in label discussion thread URL
2024-07-27 16:01:35 +01:00
Victor Hooi
2ab2766534 Fix small typo in label discussion thread URL 2024-07-28 00:56:49 +10:00
Matt Kilgore
e051352070 chore: update openapi documentation (#148) 2024-07-27 09:10:47 -04:00
Matt Kilgore
3bf1e50620 Splitting dependencies into separate docker layers (better caching) (#142)
* Initial test with NodeJS

* Fix screw up

* Try again

* Try Golang caching

* Test with some more cache
2024-07-21 10:40:03 -04:00
github-actions[bot]
42f3c88396 Update currencies.json (#143)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matt Kilgore <tankerkiller125@users.noreply.github.com>
2024-07-21 10:39:47 -04:00
Ryan Sheppard
c9f31ef934 Enable Sorting on Search By Created and Updated At (#140)
- Required updating query value to use first.value
  to set the value of the initial sort

- Ordering starts with name but can be changed to
  createdAt or updatedAt by the user
2024-07-21 09:30:03 -04:00
Ryan Sheppard
01f54aeb52 Add URL column to CSV export for items (#141)
* feat: Add URL column to CSV export for items

Enhanced the CSV export functionality to include a URL field for each item. This change required updating the export logic to generate and include item URLs based on the request's referer header.

* chore: add URL configuration to V1Controller for dynamic URL handling in item export
2024-07-21 09:29:46 -04:00
PatrickDaG
2d1016d362 Add nmprc to automatically hoist dependencies (#136) 2024-07-17 07:47:35 -04:00
Katos
b48c961ac1 Merge pull request #133 from sysadminsmedia/update-currencies
Update currencies
2024-07-14 17:25:08 +01:00
github-actions[bot]
4d47567995 Update currencies.json 2024-07-14 16:21:40 +00:00
Katos
6b2e3accf7 Merge pull request #132 from sysadminsmedia/katos/currencies-workflow
Force rebase on Update-currencies action
2024-07-14 17:21:27 +01:00
Katos
c1f8520c4f Rectify issue with Github action 2024-07-14 17:20:50 +01:00
Katos
41eb99ec40 Force rebase on Update-currencies action 2024-07-14 16:45:04 +01:00
Katos
97a74127fb Merge pull request #131 from sysadminsmedia/katos/currencies-api-sync
Skip commit attempt if currencies already synced with API
2024-07-14 16:38:31 +01:00
Katos
a9396167bf Fix my stupid mistakes.... 2024-07-14 16:35:28 +01:00
Katos
3385e5684e Update currencies automation to use a PR instead of comitting directly 2024-07-14 16:33:42 +01:00
Katos
bb9672214c Update error handling for Currencies sync, if already synced then just skip. 2024-07-14 16:25:41 +01:00
Katos
1b93672417 Merge pull request #130 from sysadminsmedia/katosdev-patch-1
Update update_currencies.py to fix error handling
2024-07-14 16:23:47 +01:00
Katos
8b1cedd4a8 Update update_currencies.py to fix error handling 2024-07-14 16:08:37 +01:00
Katos
2c34047b6d Merge pull request #129 from sysadminsmedia/katos/currencies-auto-update
Create a Github Workflow to keep currencies in sync from API
2024-07-14 16:05:48 +01:00
Katos
f0942f0714 Add error handling to API pulls 2024-07-14 16:00:25 +01:00
Katos
967e574ea8 Create a Github Workflow to keep currencies in sync from API 2024-07-14 15:52:25 +01:00
Katos
625730f37c Merge pull request #123 from sysadminsmedia/katos/fix-location-pricing
Update location pricing to include quantity in calculation
2024-07-12 21:38:09 +01:00
Katos
0a72fa95b3 Rectify issues with mismatched types float64 and int 2024-07-12 21:15:07 +01:00
Katos
c39a65ec21 Update location pricing to include quantity in calculation 2024-07-12 20:59:01 +01:00
Ikko Eltociear Ashimine
9403fb27e0 docs: update get-started.md (#106)
* docs: update get-started.md

seperated -> separated

* Apply suggestions from code review

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: Matt Kilgore <tankerkiller125@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-07-08 11:50:28 -04:00
Matt Kilgore
ab34791737 fix(docker): fixes the health check wget (#104) 2024-07-08 11:48:46 -04:00
Michael Manganiello
d03e60d580 fix: Use subrouter for API and correctly handle 404 errors (#105)
Currently, the implementation for API v1 routes has the main drawback
that any unknown path gets the fallback `notFoundHandler`, trying to
access filesystem paths.

However, for API routes specifically, we can have a subrouter, and a
default NotFound handler that returns 404.

With this change, requests to `api/v1/<unknown>` now correctly returns
status code 404 instead of 200.
2024-07-08 11:44:55 -04:00
Katos
6fcf9965bb Merge pull request #101 from sysadminsmedia/katosdev-Readme-Screenshots
Update README
2024-07-07 22:38:38 +01:00
Matt Kilgore
d53dcd37e6 Update README.md
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-07-07 17:35:55 -04:00
Katos
f3531cacb3 Update README.md 2024-07-07 22:35:19 +01:00
Katos
10cdca94dc Update README
Add screenshots to Readme file
2024-07-07 22:31:37 +01:00
Katos
0b2b7bc4fd Merge pull request #98 from sysadminsmedia/fix/docs-discord
Update Discord link to new
2024-07-07 21:55:52 +01:00
Katos
105f63487b Merge pull request #97 from 101br03k/main
set documentation url to homebox.sysadminmedia.com
2024-07-07 21:52:59 +01:00
Katos
f3c745e42e Update Discord link to new 2024-07-07 21:52:24 +01:00
A3
f3e7d7a19b set documentation url to homebox.sysadminmedia.com 2024-07-07 21:27:29 +02:00
109 changed files with 14217 additions and 8342 deletions

65
.github/scripts/update_currencies.py vendored Normal file
View File

@@ -0,0 +1,65 @@
import requests
import json
import os
def fetch_currencies():
try:
response = requests.get('https://restcountries.com/v3.1/all')
response.raise_for_status()
except requests.exceptions.Timeout:
print("Request to the API timed out.")
return []
except requests.exceptions.RequestException as e:
print(f"An error occurred while making the request: {e}")
return []
try:
countries = response.json()
except json.JSONDecodeError:
print("Failed to decode JSON from the response.")
return []
currencies_list = []
for country in countries:
country_name = country.get('name', {}).get('common')
country_currencies = country.get('currencies', {})
for currency_code, currency_info in country_currencies.items():
symbol = currency_info.get('symbol', '')
currencies_list.append({
'code': currency_code,
'local': country_name,
'symbol': symbol,
'name': currency_info.get('name')
})
return currencies_list
def save_currencies(currencies, file_path):
try:
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(currencies, f, ensure_ascii=False, indent=4)
except IOError as e:
print(f"An error occurred while writing to the file: {e}")
def load_existing_currencies(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except (IOError, json.JSONDecodeError):
return [] # Return an empty list if file doesn't exist or is invalid
def main():
save_path = 'backend/internal/core/currencies/currencies.json'
existing_currencies = load_existing_currencies(save_path)
new_currencies = fetch_currencies()
if new_currencies == existing_currencies:
print("Currencies up-to-date with API, skipping commit.")
else:
save_currencies(new_currencies, save_path)
print("Currencies updated and saved.")
if __name__ == "__main__":
main()

View File

@@ -2,7 +2,7 @@ name: Docker publish rootless
on:
schedule:
- cron: '00 6 * * *'
- cron: '00 0 * * *'
push:
branches: [ "main" ]
paths:
@@ -92,8 +92,8 @@ jobs:
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
# cache-from: type=gha
# cache-to: type=gha,mode=max
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}

View File

@@ -2,7 +2,7 @@ name: Docker publish
on:
schedule:
- cron: '00 6 * * *'
- cron: '00 0 * * *'
push:
branches: [ "main" ]
paths:
@@ -89,8 +89,8 @@ jobs:
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
# cache-from: type=gha
# cache-to: type=gha,mode=max
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.ref_name }}
COMMIT=${{ github.sha }}

100
.github/workflows/update-currencies.yml vendored Normal file
View File

@@ -0,0 +1,100 @@
name: Update Currencies
on:
push:
branches:
- main
jobs:
update-currencies:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.8'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests
- name: Run currency fetch script
run: python .github/scripts/update_currencies.py
- name: Check for changes
id: check_changes
run: |
if [[ $(git status --porcelain) ]]; then
echo "Changes detected."
echo "changes=true" >> $GITHUB_ENV
else
echo "No changes detected."
echo "changes=false" >> $GITHUB_ENV
fi
- name: Delete existing update-currencies branch
run: |
if git show-ref --verify --quiet refs/heads/update-currencies; then
git branch -D update-currencies
echo "Deleted existing update-currencies branch."
else
echo "No existing update-currencies branch to delete."
fi
- name: Create new update-currencies branch
if: env.changes == 'true'
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
# Create a new branch
git checkout -b update-currencies
git add backend/internal/core/currencies/currencies.json
git commit -m "Update currencies.json"
# Fetch the latest changes from the remote
git fetch origin
# Attempt to rebase with the latest changes
if git show-ref --verify --quiet refs/remotes/origin/update-currencies; then
if ! git rebase origin/update-currencies; then
echo "Rebase conflicts occurred. Please resolve them manually."
echo "To resolve conflicts, check out the 'update-currencies' branch locally."
exit 1
fi
else
echo "No existing remote branch 'update-currencies'. Skipping rebase."
fi
# Push the new branch to the remote
if ! git push --set-upstream origin update-currencies; then
echo "Push failed, trying to fetch and rebase again."
git fetch origin
if git show-ref --verify --quiet refs/remotes/origin/update-currencies; then
if ! git rebase origin/update-currencies; then
echo "Second rebase failed. Please resolve manually."
exit 1
fi
else
echo "No existing remote branch 'update-currencies'. Skipping rebase."
fi
if ! git push --set-upstream origin update-currencies; then
echo "Second push failed. Please resolve manually."
exit 1
fi
fi
# Create a pull request
curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-X POST \
-d '{"title": "Update currencies", "head": "update-currencies", "base": "main"}' \
https://api.github.com/repos/${{ github.repository }}/pulls
- name: Notify no changes
if: env.changes == 'false'
run: echo "Currencies up-to-date with API, skipping commit."

View File

@@ -1,13 +1,23 @@
# Node dependencies
FROM node:18-alpine AS frontend-dependencies
WORKDIR /app
RUN npm install -g pnpm
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --shamefully-hoist
# Build Nuxt
FROM node:18-alpine AS frontend-builder
WORKDIR /app
RUN npm install -g pnpm
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --shamefully-hoist
COPY frontend .
COPY --from=frontend-dependencies /app/node_modules ./node_modules
RUN pnpm build
FROM golang:alpine AS builder-dependencies
WORKDIR /go/src/app
COPY ./backend .
RUN go mod download
# Build API
FROM golang:alpine AS builder
ARG BUILD_TIME
@@ -19,10 +29,11 @@ RUN apk update && \
WORKDIR /go/src/app
COPY ./backend .
RUN go get -d -v ./...
RUN rm -rf ./app/api/public
COPY --from=frontend-builder /app/.output/public ./app/api/static/public
RUN CGO_ENABLED=0 GOOS=linux go build \
COPY --from=builder-dependencies /go/pkg/mod /go/pkg/mod
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux go build \
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
-o /go/bin/api \
-v ./app/api/*.go
@@ -51,7 +62,7 @@ HEALTHCHECK --interval=30s \
--timeout=5s \
--start-period=5s \
--retries=3 \
CMD [ "/usr/bin/wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:7745/api/v1/status" ]
CMD [ "/usr/bin/wget", "--no-verbose", "--tries=1", "-O -", "http://localhost:7745/api/v1/status" ]
VOLUME [ "/data" ]
ENTRYPOINT [ "/app/api" ]

View File

@@ -1,35 +1,42 @@
# Node dependencies
FROM node:18-alpine AS frontend-dependencies
WORKDIR /app
RUN npm install -g pnpm
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --shamefully-hoist
# Build Nuxt
FROM node:18-alpine AS frontend-builder
WORKDIR /app
RUN npm install -g pnpm
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --shamefully-hoist
COPY frontend .
COPY --from=frontend-dependencies /app/node_modules ./node_modules
RUN pnpm build
FROM golang:alpine AS builder-dependencies
WORKDIR /go/src/app
COPY ./backend .
RUN go mod download
# Build API
FROM golang:alpine AS builder
ARG BUILD_TIME
ARG COMMIT
ARG VERSION
ARG BUSYBOX_VERSION=1.36.1-r31
RUN apk update && \
apk upgrade && \
apk add --update git build-base gcc g++
WORKDIR /go/src/app
COPY ./backend .
RUN go get -d -v ./...
RUN rm -rf ./app/api/public
COPY --from=frontend-builder /app/.output/public ./app/api/static/public
RUN CGO_ENABLED=0 GOOS=linux go build \
COPY --from=builder-dependencies /go/pkg/mod /go/pkg/mod
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux go build \
-ldflags "-s -w -X main.commit=$COMMIT -X main.buildTime=$BUILD_TIME -X main.version=$VERSION" \
-o /go/bin/api \
-v ./app/api/*.go && \
chmod +x /go/bin/api && \
# create a directory so that we can copy it in the next stage
mkdir /data
-v ./app/api/*.go
FROM gcr.io/distroless/java:latest
@@ -54,7 +61,7 @@ HEALTHCHECK --interval=30s \
--timeout=5s \
--start-period=5s \
--retries=3 \
CMD [ "/usr/bin/wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:7745/api/v1/status" ]
CMD [ "/usr/bin/wget", "--no-verbose", "--tries=1", "-O -", "http://localhost:7745/api/v1/status" ]
VOLUME [ "/data" ]
# Drop root and run as low-privileged user

View File

@@ -4,9 +4,9 @@
<h1 align="center" style="margin-top: -10px"> HomeBox </h1>
<p align="center" style="width: 100;">
<a href="https://homebox.sysadminsmedia.com">Docs</a>
<a href="https://homebox.software/en/">Docs</a>
|
<a href="https://homebox.fly.dev">Demo</a>
<a href="https://demo.homebox.software">Demo</a>
|
<a href="https://discord.gg/aY4DCkpNA9">Discord</a>
</p>
@@ -16,13 +16,15 @@
Homebox is the inventory and organization system built for the Home User! With a focus on simplicity and ease of use, Homebox is the perfect solution for your home inventory, organization, and management needs. While developing this project, I've tried to keep the following principles in mind:
- _Simple_ - Homebox is designed to be simple and easy to use. No complicated setup or configuration required. Use either a single docker container, or deploy yourself by compiling the binary for your platform of choice.
- _Blazingly Fast_ - Homebox is written in Go, which makes it extremely fast and requires minimal resources to deploy. In general idle memory usage is less than 50MB for the whole container.
- _Blazingly Fast_ - Homebox is written in Go, which makes it extremely fast and requires minimal resources to deploy. In general, idle memory usage is less than 50MB for the whole container.
- _Portable_ - Homebox is designed to be portable and run on anywhere. We use SQLite and an embedded Web UI to make it easy to deploy, use, and backup.
# Screenshots
Check out screenshots of the project [here](https://imgur.com/a/5gLWt2j).
## Quick Start
[Configuration & Docker Compose](https://homebox.sysadminsmedia.com/en/quick-start.html)
[Configuration & Docker Compose](https://homebox.software/en/quick-start.html)
```bash
# If using the rootless image, ensure data
@@ -47,6 +49,11 @@ Contributions are what make the open source community such an amazing place to l
If you are not a coder, you can still contribute financially. Financial contributions help me prioritize working on this project over others and helps me know that there is a real demand for project development.
## Help us Translate
We want to make sure that Homebox is available in as many languages as possible. If you are interested in helping us translate Homebox, please help us via our [Weblate instance](https://translate.sysadminsmedia.com/projects/homebox/).
[![Translation status](http://translate.sysadminsmedia.com/widget/homebox/multi-auto.svg)](http://translate.sysadminsmedia.com/engage/homebox/)
## Credits
- Original project by [@hay-kot](https://github.com/hay-kot)

View File

@@ -57,6 +57,12 @@ func WithSecureCookies(secure bool) func(*V1Controller) {
}
}
func WithURL(url string) func(*V1Controller) {
return func(ctrl *V1Controller) {
ctrl.url = url
}
}
type V1Controller struct {
cookieSecure bool
repo *repo.AllRepos
@@ -65,6 +71,7 @@ type V1Controller struct {
isDemo bool
allowRegistration bool
bus *eventbus.EventBus
url string
}
type (
@@ -87,12 +94,6 @@ type (
}
)
func BaseURLFunc(prefix string) func(s string) string {
return func(s string) string {
return prefix + "/v1" + s
}
}
func NewControllerV1(svc *services.AllServices, repos *repo.AllRepos, bus *eventbus.EventBus, options ...func(*V1Controller)) *V1Controller {
ctrl := &V1Controller{
repo: repos,

View File

@@ -6,6 +6,7 @@ import (
"errors"
"math/big"
"net/http"
"net/url"
"strings"
"github.com/google/uuid"
@@ -333,7 +334,7 @@ func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
ctx := services.NewContext(r.Context())
csvData, err := ctrl.svc.Items.ExportCSV(r.Context(), ctx.GID)
csvData, err := ctrl.svc.Items.ExportCSV(r.Context(), ctx.GID, getHBURL(r.Header.Get("Referer"), ctrl.url))
if err != nil {
log.Err(err).Msg("failed to export items")
return validate.NewRequestError(err, http.StatusInternalServerError)
@@ -347,3 +348,26 @@ func (ctrl *V1Controller) HandleItemsExport() errchain.HandlerFunc {
return writer.WriteAll(csvData)
}
}
func getHBURL(refererHeader, fallback string) (hbURL string) {
hbURL = refererHeader
if hbURL == "" {
hbURL = fallback
}
return stripPathFromURL(hbURL)
}
// stripPathFromURL removes the path from a URL.
// ex. https://example.com/tools -> https://example.com
func stripPathFromURL(rawURL string) string {
parsedURL, err := url.Parse(rawURL)
if err != nil {
log.Err(err).Msg("failed to parse URL")
return ""
}
strippedURL := url.URL{Scheme: parsedURL.Scheme, Host: parsedURL.Host}
return strippedURL.String()
}

View File

@@ -87,12 +87,22 @@ func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc {
func (ctrl *V1Controller) GetLocationWithPrice(auth context.Context, GID uuid.UUID, ID uuid.UUID) (repo.LocationOut, error) {
var location, err = ctrl.repo.Locations.GetOneByGroup(auth, GID, ID)
if err != nil {
return repo.LocationOut{}, err
}
// Add direct child items price
totalPrice := new(big.Int)
items, err := ctrl.repo.Items.QueryByGroup(auth, GID, repo.ItemQuery{LocationIDs: []uuid.UUID{ID}})
if err != nil {
return repo.LocationOut{}, err
}
for _, item := range items.Items {
totalPrice.Add(totalPrice, big.NewInt(int64(item.PurchasePrice*100)))
// Convert item.Quantity to float64 for multiplication
quantity := float64(item.Quantity)
itemTotal := big.NewInt(int64(item.PurchasePrice * quantity * 100))
totalPrice.Add(totalPrice, itemTotal)
}
totalPriceFloat := new(big.Float).SetInt(totalPrice)
@@ -101,14 +111,15 @@ func (ctrl *V1Controller) GetLocationWithPrice(auth context.Context, GID uuid.UU
// Add price from child locations
for _, childLocation := range location.Children {
var childLocation, err = ctrl.GetLocationWithPrice(auth, GID, childLocation.ID)
var childLocationWithPrice repo.LocationOut
childLocationWithPrice, err = ctrl.GetLocationWithPrice(auth, GID, childLocation.ID)
if err != nil {
return repo.LocationOut{}, err
}
location.TotalPrice += childLocation.TotalPrice
location.TotalPrice += childLocationWithPrice.TotalPrice
}
return location, err
return location, nil
}
// HandleLocationGet godoc

View File

@@ -3,12 +3,7 @@ package main
import (
"embed"
"errors"
"io"
"mime"
"net/http"
"path"
"path/filepath"
"fmt"
"github.com/go-chi/chi/v5"
"github.com/hay-kot/httpkit/errchain"
httpSwagger "github.com/swaggo/http-swagger/v2" // http-swagger middleware
@@ -18,6 +13,11 @@ import (
_ "github.com/sysadminsmedia/homebox/backend/app/api/static/docs"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/authroles"
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
"io"
"mime"
"net/http"
"path"
"path/filepath"
)
const prefix = "/api"
@@ -47,8 +47,6 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
// =========================================================================
// API Version 1
v1Base := v1.BaseURLFunc(prefix)
v1Ctrl := v1.NewControllerV1(
a.services,
a.repos,
@@ -56,112 +54,114 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize),
v1.WithRegistration(a.conf.Options.AllowRegistration),
v1.WithDemoStatus(a.conf.Demo), // Disable Password Change in Demo Mode
v1.WithURL(fmt.Sprintf("%s:%s", a.conf.Web.Host, a.conf.Web.Port)),
)
r.Get(v1Base("/status"), chain.ToHandlerFunc(v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
Version: version,
Commit: commit,
BuildTime: buildTime,
})))
r.Route(prefix+"/v1", func(r chi.Router) {
r.Get("/status", chain.ToHandlerFunc(v1Ctrl.HandleBase(func() bool { return true }, v1.Build{
Version: version,
Commit: commit,
BuildTime: buildTime,
})))
r.Get(v1Base("/currencies"), chain.ToHandlerFunc(v1Ctrl.HandleCurrency()))
r.Get("/currencies", chain.ToHandlerFunc(v1Ctrl.HandleCurrency()))
providers := []v1.AuthProvider{
providers.NewLocalProvider(a.services.User),
}
providers := []v1.AuthProvider{
providers.NewLocalProvider(a.services.User),
}
r.Post(v1Base("/users/register"), chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration()))
r.Post(v1Base("/users/login"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin(providers...)))
r.Post("/users/register", chain.ToHandlerFunc(v1Ctrl.HandleUserRegistration()))
r.Post("/users/login", chain.ToHandlerFunc(v1Ctrl.HandleAuthLogin(providers...)))
userMW := []errchain.Middleware{
a.mwAuthToken,
a.mwRoles(RoleModeOr, authroles.RoleUser.String()),
}
userMW := []errchain.Middleware{
a.mwAuthToken,
a.mwRoles(RoleModeOr, authroles.RoleUser.String()),
}
r.Get(v1Base("/ws/events"), chain.ToHandlerFunc(v1Ctrl.HandleCacheWS(), userMW...))
r.Get(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelf(), userMW...))
r.Put(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfUpdate(), userMW...))
r.Delete(v1Base("/users/self"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfDelete(), userMW...))
r.Post(v1Base("/users/logout"), chain.ToHandlerFunc(v1Ctrl.HandleAuthLogout(), userMW...))
r.Get(v1Base("/users/refresh"), chain.ToHandlerFunc(v1Ctrl.HandleAuthRefresh(), userMW...))
r.Put(v1Base("/users/self/change-password"), chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePassword(), userMW...))
r.Get("/ws/events", chain.ToHandlerFunc(v1Ctrl.HandleCacheWS(), userMW...))
r.Get("/users/self", chain.ToHandlerFunc(v1Ctrl.HandleUserSelf(), userMW...))
r.Put("/users/self", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfUpdate(), userMW...))
r.Delete("/users/self", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfDelete(), userMW...))
r.Post("/users/logout", chain.ToHandlerFunc(v1Ctrl.HandleAuthLogout(), userMW...))
r.Get("/users/refresh", chain.ToHandlerFunc(v1Ctrl.HandleAuthRefresh(), userMW...))
r.Put("/users/self/change-password", chain.ToHandlerFunc(v1Ctrl.HandleUserSelfChangePassword(), userMW...))
r.Post(v1Base("/groups/invitations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsCreate(), userMW...))
r.Get(v1Base("/groups/statistics"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...))
r.Get(v1Base("/groups/statistics/purchase-price"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...))
r.Get(v1Base("/groups/statistics/locations"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLocations(), userMW...))
r.Get(v1Base("/groups/statistics/labels"), chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLabels(), userMW...))
r.Post("/groups/invitations", chain.ToHandlerFunc(v1Ctrl.HandleGroupInvitationsCreate(), userMW...))
r.Get("/groups/statistics", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatistics(), userMW...))
r.Get("/groups/statistics/purchase-price", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsPriceOverTime(), userMW...))
r.Get("/groups/statistics/locations", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLocations(), userMW...))
r.Get("/groups/statistics/labels", chain.ToHandlerFunc(v1Ctrl.HandleGroupStatisticsLabels(), userMW...))
// TODO: I don't like /groups being the URL for users
r.Get(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupGet(), userMW...))
r.Put(v1Base("/groups"), chain.ToHandlerFunc(v1Ctrl.HandleGroupUpdate(), userMW...))
// TODO: I don't like /groups being the URL for users
r.Get("/groups", chain.ToHandlerFunc(v1Ctrl.HandleGroupGet(), userMW...))
r.Put("/groups", chain.ToHandlerFunc(v1Ctrl.HandleGroupUpdate(), userMW...))
r.Post(v1Base("/actions/ensure-asset-ids"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureAssetID(), userMW...))
r.Post(v1Base("/actions/zero-item-time-fields"), chain.ToHandlerFunc(v1Ctrl.HandleItemDateZeroOut(), userMW...))
r.Post(v1Base("/actions/ensure-import-refs"), chain.ToHandlerFunc(v1Ctrl.HandleEnsureImportRefs(), userMW...))
r.Post(v1Base("/actions/set-primary-photos"), chain.ToHandlerFunc(v1Ctrl.HandleSetPrimaryPhotos(), userMW...))
r.Post("/actions/ensure-asset-ids", chain.ToHandlerFunc(v1Ctrl.HandleEnsureAssetID(), userMW...))
r.Post("/actions/zero-item-time-fields", chain.ToHandlerFunc(v1Ctrl.HandleItemDateZeroOut(), userMW...))
r.Post("/actions/ensure-import-refs", chain.ToHandlerFunc(v1Ctrl.HandleEnsureImportRefs(), userMW...))
r.Post("/actions/set-primary-photos", chain.ToHandlerFunc(v1Ctrl.HandleSetPrimaryPhotos(), userMW...))
r.Get(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...))
r.Post(v1Base("/locations"), chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...))
r.Get(v1Base("/locations/tree"), chain.ToHandlerFunc(v1Ctrl.HandleLocationTreeQuery(), userMW...))
r.Get(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationGet(), userMW...))
r.Put(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationUpdate(), userMW...))
r.Delete(v1Base("/locations/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLocationDelete(), userMW...))
r.Get("/locations", chain.ToHandlerFunc(v1Ctrl.HandleLocationGetAll(), userMW...))
r.Post("/locations", chain.ToHandlerFunc(v1Ctrl.HandleLocationCreate(), userMW...))
r.Get("/locations/tree", chain.ToHandlerFunc(v1Ctrl.HandleLocationTreeQuery(), userMW...))
r.Get("/locations/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLocationGet(), userMW...))
r.Put("/locations/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLocationUpdate(), userMW...))
r.Delete("/locations/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLocationDelete(), userMW...))
r.Get(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsGetAll(), userMW...))
r.Post(v1Base("/labels"), chain.ToHandlerFunc(v1Ctrl.HandleLabelsCreate(), userMW...))
r.Get(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelGet(), userMW...))
r.Put(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelUpdate(), userMW...))
r.Delete(v1Base("/labels/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleLabelDelete(), userMW...))
r.Get("/labels", chain.ToHandlerFunc(v1Ctrl.HandleLabelsGetAll(), userMW...))
r.Post("/labels", chain.ToHandlerFunc(v1Ctrl.HandleLabelsCreate(), userMW...))
r.Get("/labels/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLabelGet(), userMW...))
r.Put("/labels/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLabelUpdate(), userMW...))
r.Delete("/labels/{id}", chain.ToHandlerFunc(v1Ctrl.HandleLabelDelete(), userMW...))
r.Get(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsGetAll(), userMW...))
r.Post(v1Base("/items"), chain.ToHandlerFunc(v1Ctrl.HandleItemsCreate(), userMW...))
r.Post(v1Base("/items/import"), chain.ToHandlerFunc(v1Ctrl.HandleItemsImport(), userMW...))
r.Get(v1Base("/items/export"), chain.ToHandlerFunc(v1Ctrl.HandleItemsExport(), userMW...))
r.Get(v1Base("/items/fields"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldNames(), userMW...))
r.Get(v1Base("/items/fields/values"), chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldValues(), userMW...))
r.Get("/items", chain.ToHandlerFunc(v1Ctrl.HandleItemsGetAll(), userMW...))
r.Post("/items", chain.ToHandlerFunc(v1Ctrl.HandleItemsCreate(), userMW...))
r.Post("/items/import", chain.ToHandlerFunc(v1Ctrl.HandleItemsImport(), userMW...))
r.Get("/items/export", chain.ToHandlerFunc(v1Ctrl.HandleItemsExport(), userMW...))
r.Get("/items/fields", chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldNames(), userMW...))
r.Get("/items/fields/values", chain.ToHandlerFunc(v1Ctrl.HandleGetAllCustomFieldValues(), userMW...))
r.Get(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemGet(), userMW...))
r.Get(v1Base("/items/{id}/path"), chain.ToHandlerFunc(v1Ctrl.HandleItemFullPath(), userMW...))
r.Put(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemUpdate(), userMW...))
r.Patch(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemPatch(), userMW...))
r.Delete(v1Base("/items/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemDelete(), userMW...))
r.Get("/items/{id}", chain.ToHandlerFunc(v1Ctrl.HandleItemGet(), userMW...))
r.Get("/items/{id}/path", chain.ToHandlerFunc(v1Ctrl.HandleItemFullPath(), userMW...))
r.Put("/items/{id}", chain.ToHandlerFunc(v1Ctrl.HandleItemUpdate(), userMW...))
r.Patch("/items/{id}", chain.ToHandlerFunc(v1Ctrl.HandleItemPatch(), userMW...))
r.Delete("/items/{id}", chain.ToHandlerFunc(v1Ctrl.HandleItemDelete(), userMW...))
r.Post(v1Base("/items/{id}/attachments"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentCreate(), userMW...))
r.Put(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentUpdate(), userMW...))
r.Delete(v1Base("/items/{id}/attachments/{attachment_id}"), chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentDelete(), userMW...))
r.Post("/items/{id}/attachments", chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentCreate(), userMW...))
r.Put("/items/{id}/attachments/{attachment_id}", chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentUpdate(), userMW...))
r.Delete("/items/{id}/attachments/{attachment_id}", chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentDelete(), userMW...))
r.Get(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceLogGet(), userMW...))
r.Post(v1Base("/items/{id}/maintenance"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryCreate(), userMW...))
r.Put(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...))
r.Delete(v1Base("/items/{id}/maintenance/{entry_id}"), chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryDelete(), userMW...))
r.Get("/items/{id}/maintenance", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceLogGet(), userMW...))
r.Post("/items/{id}/maintenance", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryCreate(), userMW...))
r.Put("/items/{id}/maintenance/{entry_id}", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...))
r.Delete("/items/{id}/maintenance/{entry_id}", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryDelete(), userMW...))
r.Get(v1Base("/assets/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleAssetGet(), userMW...))
r.Get("/assets/{id}", chain.ToHandlerFunc(v1Ctrl.HandleAssetGet(), userMW...))
// Notifiers
r.Get(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleGetUserNotifiers(), userMW...))
r.Post(v1Base("/notifiers"), chain.ToHandlerFunc(v1Ctrl.HandleCreateNotifier(), userMW...))
r.Put(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleUpdateNotifier(), userMW...))
r.Delete(v1Base("/notifiers/{id}"), chain.ToHandlerFunc(v1Ctrl.HandleDeleteNotifier(), userMW...))
r.Post(v1Base("/notifiers/test"), chain.ToHandlerFunc(v1Ctrl.HandlerNotifierTest(), userMW...))
// Notifiers
r.Get("/notifiers", chain.ToHandlerFunc(v1Ctrl.HandleGetUserNotifiers(), userMW...))
r.Post("/notifiers", chain.ToHandlerFunc(v1Ctrl.HandleCreateNotifier(), userMW...))
r.Put("/notifiers/{id}", chain.ToHandlerFunc(v1Ctrl.HandleUpdateNotifier(), userMW...))
r.Delete("/notifiers/{id}", chain.ToHandlerFunc(v1Ctrl.HandleDeleteNotifier(), userMW...))
r.Post("/notifiers/test", chain.ToHandlerFunc(v1Ctrl.HandlerNotifierTest(), userMW...))
// Asset-Like endpoints
assetMW := []errchain.Middleware{
a.mwAuthToken,
a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()),
}
// Asset-Like endpoints
assetMW := []errchain.Middleware{
a.mwAuthToken,
a.mwRoles(RoleModeOr, authroles.RoleUser.String(), authroles.RoleAttachments.String()),
}
r.Get(
v1Base("/qrcode"),
chain.ToHandlerFunc(v1Ctrl.HandleGenerateQRCode(), assetMW...),
)
r.Get(
v1Base("/items/{id}/attachments/{attachment_id}"),
chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentGet(), assetMW...),
)
r.Get("/qrcode", chain.ToHandlerFunc(v1Ctrl.HandleGenerateQRCode(), assetMW...))
r.Get(
"/items/{id}/attachments/{attachment_id}",
chain.ToHandlerFunc(v1Ctrl.HandleItemAttachmentGet(), assetMW...),
)
// Reporting Services
r.Get(v1Base("/reporting/bill-of-materials"), chain.ToHandlerFunc(v1Ctrl.HandleBillOfMaterialsExport(), userMW...))
// Reporting Services
r.Get("/reporting/bill-of-materials", chain.ToHandlerFunc(v1Ctrl.HandleBillOfMaterialsExport(), userMW...))
r.NotFound(http.NotFound)
})
r.NotFound(chain.ToHandlerFunc(notFoundHandler()))
}

View File

@@ -7,25 +7,25 @@ toolchain go1.22.0
require (
ariga.io/atlas v0.19.1
entgo.io/ent v0.12.5
github.com/ardanlabs/conf/v3 v3.1.7
github.com/ardanlabs/conf/v3 v3.1.8
github.com/containrrr/shoutrrr v0.8.0
github.com/go-chi/chi/v5 v5.0.12
github.com/go-playground/validator/v10 v10.18.0
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
github.com/go-chi/chi/v5 v5.1.0
github.com/go-playground/validator/v10 v10.22.0
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
github.com/google/uuid v1.6.0
github.com/gorilla/schema v1.4.1
github.com/hay-kot/httpkit v0.0.9
github.com/mattn/go-sqlite3 v1.14.22
github.com/olahol/melody v1.1.4
github.com/hay-kot/httpkit v0.0.11
github.com/mattn/go-sqlite3 v1.14.23
github.com/olahol/melody v1.2.1
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.32.0
github.com/stretchr/testify v1.8.4
github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.9.0
github.com/swaggo/http-swagger/v2 v2.0.2
github.com/swaggo/swag v1.16.3
github.com/yeqown/go-qrcode/v2 v2.2.2
github.com/yeqown/go-qrcode/writer/standard v1.2.2
golang.org/x/crypto v0.23.0
modernc.org/sqlite v1.29.2
github.com/yeqown/go-qrcode/v2 v2.2.4
github.com/yeqown/go-qrcode/writer/standard v1.2.4
golang.org/x/crypto v0.27.0
modernc.org/sqlite v1.33.0
)
require (
@@ -65,14 +65,13 @@ require (
golang.org/x/image v0.18.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.41.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
)

View File

@@ -10,8 +10,8 @@ github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7l
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/ardanlabs/conf/v3 v3.1.7 h1:p232cF68TafoA5U9ZlbxUIhGJtGNdKHBXF80Fdqb5t0=
github.com/ardanlabs/conf/v3 v3.1.7/go.mod h1:zclexWKe0NVj6LHQ8NgDDZ7bQ1spE0KeKPFficdtAjU=
github.com/ardanlabs/conf/v3 v3.1.8 h1:r0KUV9/Hni5XdeWR2+A1BiedIDnry5CjezoqgJ0rnFQ=
github.com/ardanlabs/conf/v3 v3.1.8/go.mod h1:OIi6NK95fj8jKFPdZ/UmcPlY37JBg99hdP9o5XmNK9c=
github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec=
github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
@@ -27,8 +27,8 @@ github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
@@ -54,24 +54,23 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA=
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
@@ -82,8 +81,8 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
github.com/hay-kot/httpkit v0.0.9 h1:hu2TPY9awmIYWXxWGubaXl2U61pPvaVsm9YwboBRGu0=
github.com/hay-kot/httpkit v0.0.9/go.mod h1:AD22YluZrvBDxmtB3Pw2SOyp3A2PZqcmBZa0+COrhoU=
github.com/hay-kot/httpkit v0.0.11 h1:ZdB2uqsFBSDpfUoClGK5c5orjBjQkEVSXh7fZX5FKEk=
github.com/hay-kot/httpkit v0.0.11/go.mod h1:0kZdk5/swzdfqfg2c6pBWimcgeJ9PTyO97EbHnYl2Sw=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -111,15 +110,15 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olahol/melody v1.1.4 h1:RQHfKZkQmDxI0+SLZRNBCn4LiXdqxLKRGSkT8Dyoe/E=
github.com/olahol/melody v1.1.4/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
github.com/olahol/melody v1.2.1 h1:xdwRkzHxf+B0w4TKbGpUSSkV516ZucQZJIWLztOWICQ=
github.com/olahol/melody v1.2.1/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
@@ -133,8 +132,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -145,39 +144,39 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg=
github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ=
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/yeqown/go-qrcode/v2 v2.2.2 h1:0comk6jEwi0oWNhKEmzx4JI+Q7XIneAApmFSMKWmSVc=
github.com/yeqown/go-qrcode/v2 v2.2.2/go.mod h1:2Qsk2APUCPne0TsRo40DIkI5MYnbzYKCnKGEFWrxd24=
github.com/yeqown/go-qrcode/writer/standard v1.2.2 h1:gyzunKXgC0ZUpKqQFUImbAEwewAiwNCkxFEKZV80Kt4=
github.com/yeqown/go-qrcode/writer/standard v1.2.2/go.mod h1:bbVRiBJSRPj4UBZP/biLG7JSd9kHqXjErk1eakAMnRA=
github.com/yeqown/go-qrcode/v2 v2.2.4 h1:cXdYlrhzHzVAnJHiwr/T6lAUmS9MtEStjEZBjArrvnc=
github.com/yeqown/go-qrcode/v2 v2.2.4/go.mod h1:uHpt9CM0V1HeXLz+Wg5MN50/sI/fQhfkZlOM+cOTHxw=
github.com/yeqown/go-qrcode/writer/standard v1.2.4 h1:41e/aLr1AMVWlug6oUMkDg2r0+dv5ofB7UaTkekKZBc=
github.com/yeqown/go-qrcode/writer/standard v1.2.4/go.mod h1:H8nLSGYUWBpNyBPjDcJzAanMzYBBYMFtrU2lwoSRn+k=
github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0=
github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM=
github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA=
github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
@@ -194,16 +193,16 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.29.2 h1:xgBSyA3gemwgP31PWFfFjtBorQNYpeypGdoSDjXhrgI=
modernc.org/sqlite v1.29.2/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/sqlite v1.33.0 h1:WWkA/T2G17okiLGgKAj4/RMIvgyMT19yQ038160IeYk=
modernc.org/sqlite v1.33.0/go.mod h1:9uQ9hF/pCZoYZK73D/ud5Z7cIRIILSZI8NdIemVMTX8=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@ type ExportCSVRow struct {
LabelStr LabelString `csv:"HB.labels"`
AssetID repo.AssetID `csv:"HB.asset_id"`
Archived bool `csv:"HB.archived"`
URL string `csv:"HB.url"`
Name string `csv:"HB.name"`
Quantity int `csv:"HB.quantity"`

View File

@@ -153,7 +153,7 @@ func (s *IOSheet) Read(data io.Reader) error {
}
// ReadItems writes the sheet to a writer.
func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.UUID, repos *repo.AllRepos) error {
func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.UUID, repos *repo.AllRepos, hbURL string) error {
s.Rows = make([]ExportCSVRow, len(items))
extraHeaders := map[string]struct{}{}
@@ -178,6 +178,8 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
labelString[i] = l.Name
}
url := generateItemURL(item, hbURL)
customFields := make([]ExportItemFields, len(item.Fields))
for i, f := range item.Fields {
@@ -201,6 +203,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
Description: item.Description,
Insured: item.Insured,
Archived: item.Archived,
URL: url,
PurchasePrice: item.PurchasePrice,
PurchaseFrom: item.PurchaseFrom,
@@ -219,6 +222,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
SoldPrice: item.SoldPrice,
SoldNotes: item.SoldNotes,
Notes: item.Notes,
Fields: customFields,
}
}
@@ -252,6 +256,14 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
return nil
}
func generateItemURL(item repo.ItemOut, d string) string {
url := ""
if item.ID != uuid.Nil {
url = fmt.Sprintf("%s/item/%s", d, item.ID.String())
}
return url
}
// CSV writes the current sheet to a 2d array, for compatibility with TSV/CSV files.
func (s *IOSheet) CSV() ([][]string, error) {
memcsv := make([][]string, len(s.Rows)+1)

View File

@@ -329,7 +329,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
return finished, nil
}
func (svc *ItemService) ExportCSV(ctx context.Context, GID uuid.UUID) ([][]string, error) {
func (svc *ItemService) ExportCSV(ctx context.Context, GID uuid.UUID, hbURL string) ([][]string, error) {
items, err := svc.repo.Items.GetAll(ctx, GID)
if err != nil {
return nil, err
@@ -337,7 +337,7 @@ func (svc *ItemService) ExportCSV(ctx context.Context, GID uuid.UUID) ([][]strin
sheet := reporting.IOSheet{}
err = sheet.ReadItems(ctx, items, GID, svc.repo)
err = sheet.ReadItems(ctx, items, GID, svc.repo, hbURL)
if err != nil {
return nil, err
}

View File

@@ -29,9 +29,9 @@ func (tp *TemplateProps) Set(key, value string) {
func DefaultTemplateData() TemplateProps {
return TemplateProps{
Defaults: TemplateDefaults{
CompanyName: "Haybytes.com",
CompanyName: "sysadminsmedia.com",
CompanyAddress: "123 Main St, Anytown, CA 12345",
CompanyURL: "https://haybytes.com",
CompanyURL: "https://sysadminsmedia.com",
ActivateAccountURL: "https://google.com",
UnsubscribeURL: "https://google.com",
},

View File

@@ -6,7 +6,7 @@ export default defineConfig({
description: "A simple home inventory management software",
lastUpdated: true,
sitemap: {
hostname: 'https://homebox.sysadminsmedia.com',
hostname: 'https://homebox.software',
},
locales: {
@@ -27,7 +27,8 @@ export default defineConfig({
},
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'API', link: 'https://redocly.github.io/redoc/?url=https://raw.githubusercontent.com/sysadminsmedia/homebox/main/docs/docs/api/openapi-2.0.json' }
{ text: 'API', link: 'https://redocly.github.io/redoc/?url=https://raw.githubusercontent.com/sysadminsmedia/homebox/main/docs/docs/api/openapi-2.0.json' },
{ text: 'Demo', link: 'https://demo.homebox.software' },
],
sidebar: {
@@ -36,6 +37,8 @@ export default defineConfig({
text: 'Getting Started',
items: [
{ text: 'Quick Start', link: '/en/quick-start' },
{ text: 'Installation', link: '/en/installation' },
{ text: 'Configure Homebox', link: '/en/configure-homebox' },
{ text: 'Tips and Tricks', link: '/en/tips-tricks' }
]
},
@@ -56,8 +59,8 @@ export default defineConfig({
},
socialLinks: [
{ icon: 'discord', link: 'https://discord.gg/aY4DCkpNA9' },
{ icon: 'github', link: 'https://github.com/sysadminsmedia/homebox' },
{ icon: 'discord', link: 'https://discord.homebox.software' },
{ icon: 'github', link: 'https://git.homebox.software' },
{ icon: 'mastodon', link: 'https://noc.social/@sysadminszone' },
]
}

View File

@@ -1683,12 +1683,14 @@
"parameters": [
{
"type": "string",
"example": "admin@admin.com",
"description": "string",
"name": "username",
"in": "formData"
},
{
"type": "string",
"example": "admin",
"description": "string",
"name": "password",
"in": "formData"

View File

@@ -0,0 +1,59 @@
# Configure Homebox
## Env Variables & Configuration
| Variable | Default | Description |
| ------------------------------------ | ---------------------- | ---------------------------------------------------------------------------------- |
| HBOX_MODE | `production` | application mode used for runtime behavior can be one of: `development`, `production` |
| HBOX_WEB_PORT | 7745 | port to run the web server on, if you're using docker do not change this |
| HBOX_WEB_HOST | | host to run the web server on, if you're using docker do not change this |
| HBOX_OPTIONS_ALLOW_REGISTRATION | true | allow users to register themselves |
| HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID | true | auto-increments the asset_id field for new items |
| HBOX_OPTIONS_CURRENCY_CONFIG | | json configuration file containing additional currencie |
| HBOX_WEB_MAX_UPLOAD_SIZE | 10 | maximum file upload size supported in MB |
| HBOX_WEB_READ_TIMEOUT | 10s | Read timeout of HTTP sever |
| HBOX_WEB_WRITE_TIMEOUT | 10s | Write timeout of HTTP server |
| HBOX_WEB_IDLE_TIMEOUT | 30s | Idle timeout of HTTP server |
| HBOX_STORAGE_DATA | /data/ | path to the data directory, do not change this if you're using docker |
| HBOX_STORAGE_SQLITE_URL | /data/homebox.db?_fk=1 | sqlite database url, if you're using docker do not change this |
| HBOX_LOG_LEVEL | `info` | log level to use, can be one of `trace`, `debug`, `info`, `warn`, `error`, `critical` |
| HBOX_LOG_FORMAT | `text` | log format to use, can be one of: `text`, `json` |
| HBOX_MAILER_HOST | | email host to use, if not set no email provider will be used |
| HBOX_MAILER_PORT | 587 | email port to use |
| HBOX_MAILER_USERNAME | | email user to use |
| HBOX_MAILER_PASSWORD | | email password to use |
| HBOX_MAILER_FROM | | email from address to use |
| HBOX_SWAGGER_HOST | 7745 | swagger host to use, if not set swagger will be disabled |
| HBOX_SWAGGER_SCHEMA | `http` | swagger schema to use, can be one of: `http`, `https` |
::: tip "CLI Arguments"
If you're deploying without docker you can use command line arguments to configure the application. Run `homebox --help` for more information.
```sh
Usage: api [options] [arguments]
OPTIONS
--mode/$HBOX_MODE <string> (default: development)
--web-port/$HBOX_WEB_PORT <string> (default: 7745)
--web-host/$HBOX_WEB_HOST <string>
--web-max-upload-size/$HBOX_WEB_MAX_UPLOAD_SIZE <int> (default: 10)
--storage-data/$HBOX_STORAGE_DATA <string> (default: ./.data)
--storage-sqlite-url/$HBOX_STORAGE_SQLITE_URL <string> (default: ./.data/homebox.db?_fk=1)
--log-level/$HBOX_LOG_LEVEL <string> (default: info)
--log-format/$HBOX_LOG_FORMAT <string> (default: text)
--mailer-host/$HBOX_MAILER_HOST <string>
--mailer-port/$HBOX_MAILER_PORT <int>
--mailer-username/$HBOX_MAILER_USERNAME <string>
--mailer-password/$HBOX_MAILER_PASSWORD <string>
--mailer-from/$HBOX_MAILER_FROM <string>
--swagger-host/$HBOX_SWAGGER_HOST <string> (default: localhost:7745)
--swagger-scheme/$HBOX_SWAGGER_SCHEME <string> (default: http)
--demo/$HBOX_DEMO <bool>
--debug-enabled/$HBOX_DEBUG_ENABLED <bool> (default: false)
--debug-port/$HBOX_DEBUG_PORT <string> (default: 4000)
--options-allow-registration/$HBOX_OPTIONS_ALLOW_REGISTRATION <bool> (default: true)
--options-auto-increment-asset-id/$HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID <bool> (default: true)
--options-currency-config/$HBOX_OPTIONS_CURRENCY_CONFIG <string>
--help/-h display this help message
```
:::

View File

@@ -47,23 +47,28 @@ type checking `task ui:check`
## Documentation
We use [Vitepress](https://vitepress.dev/) for the web documentation of homebox. Anyone is welcome to contribute the documentation if they wish.
For documentation contributions you only need NodeJS and PNPM.
For documentation contributions, you only need Node.js and PNPM.
::: info Notes
- Languages are seperated by folder (e.g `/en`, `/fr`, etc.)
- Languages are separated by folder (e.g `/en`, `/fr`, etc.)
- The Sidebar must be updated on a per language basis
- Each languages files can be named independently (slugs can match the language)
- Each language's files can be named independently (slugs can match the language)
- The `public/_redirects` file is used to redirect the default to english
- Redirects can also be configured per language by adding `Language=` after the redirect code
:::
## Translations
We use our own [Weblate instance](https://translate.sysadminsmedia.com/projects/homebox/) for translations. If you would like to help translate Homebox, please visit the Weblate instance and help us translate the project.
[![Translation status](http://translate.sysadminsmedia.com/widget/homebox/multi-auto.svg)](http://translate.sysadminsmedia.com/engage/homebox/)
## Branch Flow
We use the `main` branch as the development branch. All PRs should be made to the `main` branch form a feature branch.
We use the `main` branch as the development branch. All PRs should be made to the `main` branch from a feature branch.
To create a pull request you can use the following steps:
1. Fork the repo and create a new branch from `main`
2. If you added code that should be tested, add tests
3. If you've changed APIs update the documentation
3. If you've changed APIs, update the documentation
4. Ensure that the test suite and linters pass
5. Create your PR

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -15,6 +15,9 @@ hero:
- theme: alt
text: Tips and Tricks
link: /en/tips-tricks
- theme: alt
text: Try It Out
link: https://demo.homebox.software
features:
- title: Add/Update/Delete Items
@@ -28,9 +31,11 @@ features:
- title: Custom labeling and locations
details: Use custom labels and locations to organize items
- title: Multi-Tenant Support
details: All users are in a group, and can only see what's in the group. Invite family memebers or share an instance with friends.
details: All users are in a group, and can only see what's in the group. Invite family members or share an instance with friends.
---
![HomeBox Home Screen Screenshot](images/home-screen.png)
Homebox is the inventory and organization system built for the Home User! With a focus on simplicity and ease of use, Homebox is the perfect solution for your home inventory, organization, and management needs. While developing this project, I've tried to keep the following principles in mind:
- _Simple_ - Homebox is designed to be simple and easy to use. No complicated setup or configuration required. Use either a single docker container, or deploy yourself by compiling the binary for your platform of choice.

99
docs/en/installation.md Normal file
View File

@@ -0,0 +1,99 @@
# Installation
There are two main ways to run the application.
1. As a [Docker](https://www.docker.com/) container.
2. Using the correct executable for your platform by downloading it from the [Releases](https://github.com/sysadminsmedia/homebox/releases).
## Docker
The following instructions assume Docker is already installed on your system. See [(Docker's official installation guide)](https://docs.docker.com/engine/install/)
The official image is `ghcr.io/sysadminsmedia/homebox:latest`. For each image there are two tags, respectively the regular tag and $TAG-rootless, which uses a non-root image.
### Docker Run
Great for testing out the application, but not recommended for stable use. Checkout the docker-compose below for the recommended deployment.
```sh
# If using the rootless image, ensure data
# folder has correct permissions
$ mkdir -p /path/to/data/folder
$ chown 65532:65532 -R /path/to/data/folder
# ---------------------------------------
# Run the image
$ docker run -d \
--name homebox \
--restart unless-stopped \
--publish 3100:7745 \
--env TZ=Europe/Bucharest \
--volume /path/to/data/folder/:/data \
ghcr.io/sysadminsmedia/homebox:latest
# ghcr.io/sysadminsmedia/homebox:latest-rootless
```
### Docker Compose
1. Create a `docker-compose.yml` file.
```yaml
services:
homebox:
image: ghcr.io/sysadminsmedia/homebox:latest
# image: ghcr.io/sysadminsmedia/homebox:latest-rootless
container_name: homebox
restart: always
environment:
- HBOX_LOG_LEVEL=info
- HBOX_LOG_FORMAT=text
- HBOX_WEB_MAX_UPLOAD_SIZE=10
volumes:
- homebox-data:/data/
ports:
- 3100:7745
volumes:
homebox-data:
driver: local
```
::: info
If you use the `rootless` image, and instead of using named volumes you would prefer using a hostMount directly (e.g., `volumes: [ /path/to/data/folder:/data ]`) you need to `chown` the chosen directory in advance to the `65532` user (as shown in the Docker example above).
:::
::: warning
If you have previously set up docker compose with the `HBOX_WEB_READ_TIMEOUT`, `HBOX_WEB_WRITE_TIMEOUT`, or `HBOX_IDLE_TIMEOUT` options, and you were previously using the hay-kot image, please note that you will have to add an `s` for seconds or `m` for minutes to the end of the integers. A dependency update removed the defaultation to seconds and it now requires an explicit duration time.
:::
2. While in the same folder as docker-compose.yml, start the container by running:
```bash
docker compose up --detach
```
3. Navigate to `http://server.local.ip.address:3100/` to access the web interface. (replace with the right IP address).
You can learn more about Docker by [reading the official Docker documentation.](https://docs.docker.com/)
## Windows
1. Download the appropriate release for your CPU architecture from the [releases page on GitHub](https://github.com/sysadminsmedia/homebox/releases).
2. Extract the archive.
3. Run `homebox.exe`. This will start the server on port 7745.
4. You can test it by accessing http://localhost:7745.
## Linux
1. Download the appropriate release for your CPU architecture from the [releases page on GitHub](https://github.com/sysadminsmedia/homebox/releases).
2. Extract the archive.
3. Run the `homebox` executable.
4. The web interface will be accessible on port 7745 by default. Access the page by navigating to `http://server.local.ip.address:7745/` (replace with the right ip address)
## macOS
1. Download the appropriate release for your CPU architecture from the [releases page on GitHub](https://github.com/sysadminsmedia/homebox/releases). (Use `homebox_Darwin_x86_64.tar.gz` for Intel-based macs and `homebox_Darwin_arm64.tar.gz` for Apple Silicon)
2. Extract the archive.
3. Run the `homebox` executable.
4. The web interface will be accessible on port 7745 by default. Access the page by navigating to `http://local.ip.address:7745/` (replace with the right ip address)

View File

@@ -1,113 +1,12 @@
# Quick Start
## Docker Run
1. Install Homebox either by using [the latest Docker image](https://ghcr.io/sysadminsmedia/homebox:latest), or by downloading the correct executable for your Operating System from the [Releases](https://github.com/sysadminsmedia/homebox/releases). (See [Installation](./installation) for more details)
Great for testing out the application, but not recommended for stable use. Checkout the docker-compose for the recommended deployment.
2. Browse to `http://SERVER_IP:3100` (if Using Docker) or `http://SERVER_IP:7745` (if installed locally) to access the included web User Interface.
For each image there are two tags, respectively the regular tag and $TAG-rootless, which uses a non-root image.
3. Register your first user.
```sh
# If using the rootless image, ensure data
# folder has correct permissions
$ mkdir -p /path/to/data/folder
$ chown 65532:65532 -R /path/to/data/folder
# ---------------------------------------
# Run the image
$ docker run -d \
--name homebox \
--restart unless-stopped \
--publish 3100:7745 \
--env TZ=Europe/Bucharest \
--volume /path/to/data/folder/:/data \
ghcr.io/sysadminsmedia/homebox:latest
# ghcr.io/sysadminsmedia/homebox:latest-rootless
4. Login with the user you just created and start adding your locations and items!
```
## Docker-Compose
```yaml
services:
homebox:
image: ghcr.io/sysadminsmedia/homebox:latest
# image: ghcr.io/sysadminsmedia/homebox:latest-rootless
container_name: homebox
restart: always
environment:
- HBOX_LOG_LEVEL=info
- HBOX_LOG_FORMAT=text
- HBOX_WEB_MAX_UPLOAD_SIZE=10
volumes:
- homebox-data:/data/
ports:
- 3100:7745
volumes:
homebox-data:
driver: local
```
::: info
If you use the `rootless` image, and instead of using named volumes you would prefer using a hostMount directly (e.g., `volumes: [ /path/to/data/folder:/data ]`) you need to `chown` the chosen directory in advance to the `65532` user (as shown in the Docker example above).
:::
::: warning
If you have previously set up docker compose with the `HBOX_WEB_READ_TIMEOUT`, `HBOX_WEB_WRITE_TIMEOUT`, or `HBOX_IDLE_TIMEOUT` options, and you were previously using the hay-kot image, please note that you will have to add an `s` for seconds or `m` for minutes to the end of the integers. A dependency update removed the defaultation to seconds and it now requires an explicit duration time.
:::
## Env Variables & Configuration
| Variable | Default | Description |
| ------------------------------------ | ---------------------- | ---------------------------------------------------------------------------------- |
| HBOX_MODE | production | application mode used for runtime behavior can be one of: development, production |
| HBOX_WEB_PORT | 7745 | port to run the web server on, if you're using docker do not change this |
| HBOX_WEB_HOST | | host to run the web server on, if you're using docker do not change this |
| HBOX_OPTIONS_ALLOW_REGISTRATION | true | allow users to register themselves |
| HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID | true | auto-increments the asset_id field for new items |
| HBOX_OPTIONS_CURRENCY_CONFIG | | json configuration file containing additional currencie |
| HBOX_WEB_MAX_UPLOAD_SIZE | 10 | maximum file upload size supported in MB |
| HBOX_WEB_READ_TIMEOUT | 10s | Read timeout of HTTP sever |
| HBOX_WEB_WRITE_TIMEOUT | 10s | Write timeout of HTTP server |
| HBOX_WEB_IDLE_TIMEOUT | 30s | Idle timeout of HTTP server |
| HBOX_STORAGE_DATA | /data/ | path to the data directory, do not change this if you're using docker |
| HBOX_STORAGE_SQLITE_URL | /data/homebox.db?_fk=1 | sqlite database url, if you're using docker do not change this |
| HBOX_LOG_LEVEL | info | log level to use, can be one of trace, debug, info, warn, error, critical |
| HBOX_LOG_FORMAT | text | log format to use, can be one of: text, json |
| HBOX_MAILER_HOST | | email host to use, if not set no email provider will be used |
| HBOX_MAILER_PORT | 587 | email port to use |
| HBOX_MAILER_USERNAME | | email user to use |
| HBOX_MAILER_PASSWORD | | email password to use |
| HBOX_MAILER_FROM | | email from address to use |
| HBOX_SWAGGER_HOST | 7745 | swagger host to use, if not set swagger will be disabled |
| HBOX_SWAGGER_SCHEMA | http | swagger schema to use, can be one of: http, https |
::: tip "CLI Arguments"
If you're deploying without docker you can use command line arguments to configure the application. Run `homebox --help` for more information.
```sh
Usage: api [options] [arguments]
OPTIONS
--mode/$HBOX_MODE <string> (default: development)
--web-port/$HBOX_WEB_PORT <string> (default: 7745)
--web-host/$HBOX_WEB_HOST <string>
--web-max-upload-size/$HBOX_WEB_MAX_UPLOAD_SIZE <int> (default: 10)
--storage-data/$HBOX_STORAGE_DATA <string> (default: ./.data)
--storage-sqlite-url/$HBOX_STORAGE_SQLITE_URL <string> (default: ./.data/homebox.db?_fk=1)
--log-level/$HBOX_LOG_LEVEL <string> (default: info)
--log-format/$HBOX_LOG_FORMAT <string> (default: text)
--mailer-host/$HBOX_MAILER_HOST <string>
--mailer-port/$HBOX_MAILER_PORT <int>
--mailer-username/$HBOX_MAILER_USERNAME <string>
--mailer-password/$HBOX_MAILER_PASSWORD <string>
--mailer-from/$HBOX_MAILER_FROM <string>
--swagger-host/$HBOX_SWAGGER_HOST <string> (default: localhost:7745)
--swagger-scheme/$HBOX_SWAGGER_SCHEME <string> (default: http)
--demo/$HBOX_DEMO <bool>
--debug-enabled/$HBOX_DEBUG_ENABLED <bool> (default: false)
--debug-port/$HBOX_DEBUG_PORT <string> (default: 4000)
--options-allow-registration/$HBOX_OPTIONS_ALLOW_REGISTRATION <bool> (default: true)
--options-auto-increment-asset-id/$HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID <bool> (default: true)
--options-currency-config/$HBOX_OPTIONS_CURRENCY_CONFIG <string>
--help/-h display this help message
```
:::
> [!TIP]
> If you want other users to see your items and locations, they will need to sign up using your invite link, otherwise they will only see their own items. Go to the **Profile** section in the left navigation bar and under **User Profile**, click **Generate Invite Link**.

View File

@@ -21,6 +21,8 @@ Homebox provides the option to auto-set asset IDs, this is the default behavior.
Example ID: `000-001`
To search for an Asset ID: type `#` in the search bar followed by the ID you're searching for, e.g. `#000-001`.
Asset IDs are partially managed by Homebox, but have a flexible implementation to allow for unique use cases. IDs are non-unique at the database level, so there is nothing stopping a user from manually setting duplicate IDs for various items. There are two recommended approaches to manage Asset IDs:
### 1. Auto Incrementing IDs

View File

@@ -11,6 +11,7 @@ module.exports = {
"@nuxtjs/eslint-config-typescript",
"plugin:vue/vue3-recommended",
"plugin:prettier/recommended",
"plugin:tailwindcss/recommended",
],
parserOptions: {
ecmaVersion: "latest",

1
frontend/.npmrc Normal file
View File

@@ -0,0 +1 @@
shamefully-hoist=true

View File

@@ -68,23 +68,23 @@
<LabelCreateModal v-model="modals.label" />
<LocationCreateModal v-model="modals.location" />
<div class="bg-neutral absolute shadow-xl top-0 h-[20rem] max-h-96 -z-10 w-full"></div>
<div class="absolute top-0 -z-10 h-80 max-h-96 w-full bg-neutral shadow-xl"></div>
<BaseContainer cmp="header" class="py-6 max-w-none">
<BaseContainer cmp="header" class="max-w-none py-6">
<BaseContainer>
<NuxtLink to="/home">
<h2 class="mt-1 text-4xl font-bold tracking-tight text-neutral-content sm:text-5xl lg:text-6xl flex">
<h2 class="mt-1 flex text-4xl font-bold tracking-tight text-neutral-content sm:text-5xl lg:text-6xl">
HomeB
<AppLogo class="w-12 -mb-4" />
<AppLogo class="-mb-4 w-12" />
x
</h2>
</NuxtLink>
<div class="ml-1 mt-2 text-lg text-neutral-content/75 space-x-2">
<div class="ml-1 mt-2 space-x-2 text-lg text-neutral-content/75">
<template v-for="link in links">
<NuxtLink
v-if="!link.action"
:key="link.name"
class="hover:text-base-content transition-color duration-200 italic"
class="italic transition-colors duration-200 hover:text-base-content"
:to="link.href"
>
{{ link.name }}
@@ -93,7 +93,7 @@
v-else
:key="link.name + 'link'"
for="location-form-modal"
class="hover:text-base-content transition-color duration-200 italic"
class="italic transition-colors duration-200 hover:text-base-content"
@click="link.action"
>
{{ link.name }}
@@ -101,15 +101,15 @@
<span v-if="!link.last" :key="link.name"> / </span>
</template>
</div>
<div class="flex mt-6">
<div class="mt-6 flex">
<div class="dropdown">
<label tabindex="0" class="btn btn-primary btn-sm">
<span>
<MdiPlus class="mr-1 -ml-1" />
<MdiPlus class="-ml-1 mr-1" />
</span>
Create
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
<ul tabindex="0" class="dropdown-content menu rounded-box w-52 bg-base-100 p-2 shadow">
<li v-for="btn in dropdown" :key="btn.name">
<button @click="btn.action">
{{ btn.name }}

View File

@@ -1,15 +1,14 @@
<template>
<BaseModal v-model="dialog">
<template #title> Import CSV File </template>
<template #title> {{ $t("components.app.import_dialog.title") }} </template>
<p>
Import a CSV file containing your items, labels, and locations. See documentation for more information on the
required format.
{{ $t("components.app.import_dialog.description") }}
</p>
<div class="alert alert-warning shadow-lg mt-4">
<div class="alert alert-warning mt-4 shadow-lg">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current flex-shrink-0 h-6 w-6 mb-auto"
class="mb-auto size-6 shrink-0 stroke-current"
fill="none"
viewBox="0 0 24 24"
>
@@ -21,8 +20,7 @@
/>
</svg>
<span class="text-sm">
Behavior for imports with existing import_refs has changed. If an import_ref is present in the CSV file, the
item will be updated with the values in the CSV file.
{{ $t("components.app.import_dialog.change_warning") }}
</span>
</div>
</div>
@@ -32,16 +30,16 @@
<input ref="importRef" type="file" class="hidden" accept=".csv,.tsv" @change="setFile" />
<BaseButton type="button" @click="uploadCsv">
<MdiUpload class="h-5 w-5 mr-2" />
Upload
<MdiUpload class="mr-2 size-5" />
{{ $t("components.app.import_dialog.upload") }}
</BaseButton>
<p class="text-center pt-4 -mb-5">
<p class="-mb-5 pt-4 text-center">
{{ importCsv?.name }}
</p>
</div>
<div class="modal-action">
<BaseButton type="submit" :disabled="!importCsv"> Submit </BaseButton>
<BaseButton type="submit" :disabled="!importCsv"> {{ $t("global.submit") }} </BaseButton>
</div>
</form>
</BaseModal>

View File

@@ -1,5 +1,5 @@
<template>
<div class="force-above fixed top-2 right-2 w-[300px]">
<div class="fixed right-2 top-2 z-[9999] w-[300px]">
<TransitionGroup name="notify" tag="div">
<div
v-for="(notify, index) in notifications.slice(0, 4)"
@@ -14,14 +14,14 @@
>
<div class="flex gap-1">
<template v-if="notify.type == 'success'">
<MdiCheckboxMarkedCircle class="h-5 w-5" />
<MdiCheckboxMarkedCircle class="size-5" />
</template>
<template v-if="notify.type == 'info'">
<MdiInformationSlabCircle class="h-5 w-5" />
<MdiInformationSlabCircle class="size-5" />
</template>
<template v-if="notify.type == 'error'">
<MdiAlert class="h-5 w-5" />
<MdiAlert class="size-5" />
</template>
{{ notify.message }}
</div>
@@ -41,10 +41,6 @@
</script>
<style scoped>
.force-above {
z-index: 9999;
}
.notify-move,
.notify-enter-active,
.notify-leave-active {

View File

@@ -1,13 +1,13 @@
<template>
<div class="card bg-base-100 shadow-xl sm:rounded-lg">
<div class="card bg-base-100 shadow-xl rounded-lg">
<div v-if="$slots.title" class="px-4 py-5 sm:px-6">
<component :is="collapsable ? 'button' : 'div'" v-on="collapsable ? { click: toggle } : {}">
<h3 class="text-lg font-medium leading-6 flex items-center">
<h3 class="flex items-center text-lg font-medium leading-6">
<slot name="title"></slot>
<template v-if="collapsable">
<span class="ml-2 swap swap-rotate" :class="`${collapsed ? 'swap-active' : ''}`">
<MdiChevronRight class="h-6 w-6 swap-on" />
<MdiChevronDown class="h-6 w-6 swap-off" />
<span class="swap swap-rotate ml-2" :class="`${collapsed ? 'swap-active' : ''}`">
<MdiChevronRight class="swap-on size-6" />
<MdiChevronDown class="swap-off size-6" />
</span>
</template>
</h3>

View File

@@ -8,7 +8,7 @@
</script>
<template>
<component :is="cmp" class="container max-w-6xl mx-auto px-3">
<component :is="cmp" class="container mx-auto mt-10 max-w-6xl px-3">
<slot />
</component>
</template>

View File

@@ -1,11 +1,11 @@
<template>
<div class="z-[999]">
<input :id="modalId" v-model="modal" type="checkbox" class="modal-toggle" />
<div class="modal modal-bottom sm:modal-middle overflow-visible">
<div class="modal-box overflow-visible relative">
<button :for="modalId" class="btn btn-sm btn-circle absolute right-2 top-2" @click="close"></button>
<div class="modal modal-bottom overflow-visible sm:modal-middle">
<div class="modal-box relative overflow-visible">
<button :for="modalId" class="btn btn-circle btn-sm absolute right-2 top-2" @click="close"></button>
<h3 class="font-bold text-lg">
<h3 class="text-lg font-bold">
<slot name="title"></slot>
</h3>
<slot> </slot>

View File

@@ -1,7 +1,7 @@
<template>
<div class="pb-3">
<h3
class="text-3xl font-bold tracking-tight flex items-center"
class="flex items-center text-3xl font-bold tracking-tight"
:class="{
'text-neutral-content': dark,
'text-content': !dark,

View File

@@ -1,5 +1,5 @@
<template>
<div class="grid grid-cols-1 md:grid-cols-4 gap-10 py-6">
<div class="grid grid-cols-1 gap-10 py-6 md:grid-cols-4">
<div class="col-span-3">
<h4 class="mb-1 text-lg font-semibold">
<slot name="title"></slot>

View File

@@ -8,13 +8,13 @@
<input
v-model="internalSearch"
tabindex="0"
class="input w-full items-center flex flex-wrap border border-gray-400 rounded-lg"
class="input flex w-full flex-wrap items-center rounded-lg border border-gray-400"
@keyup.enter="selectFirst"
/>
<button
v-if="!!modelValue && Object.keys(modelValue).length !== 0"
style="transform: translateY(-50%)"
class="top-1/2 absolute right-2 btn btn-xs btn-circle no-animation"
class="btn btn-circle btn-xs no-animation absolute right-2 top-1/2"
@click="clear"
>
x
@@ -23,7 +23,7 @@
<ul
tabindex="0"
style="display: inline"
class="dropdown-content mb-1 menu shadow border border-gray-400 rounded bg-base-100 w-full z-[9999] max-h-60 overflow-y-scroll"
class="dropdown-content menu z-[9999] mb-1 max-h-60 w-full overflow-y-scroll rounded border border-gray-400 bg-base-100 shadow"
>
<li v-for="(obj, idx) in filtered" :key="idx">
<div type="button" @click="select(obj)">

View File

@@ -7,7 +7,7 @@
<div class="relative">
<ComboboxInput
:display-value="i => extractDisplay(i as SupportValues)"
class="w-full input input-bordered"
class="input input-bordered w-full"
@change="search = $event.target.value"
/>
<button
@@ -16,14 +16,14 @@
class="absolute inset-y-0 right-6 flex items-center rounded-r-md px-2 focus:outline-none"
@click="clear"
>
<MdiClose class="w-5 h-5" />
<MdiClose class="size-5" />
</button>
<ComboboxButton class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
<MdiChevronDown class="w-5 h-5" />
<MdiChevronDown class="size-5" />
</ComboboxButton>
<ComboboxOptions
v-if="computedItems.length > 0"
class="absolute dropdown-content z-10 mt-2 max-h-60 w-full overflow-auto rounded-md card bg-base-100 border border-gray-400"
class="card dropdown-content absolute z-10 mt-2 max-h-60 w-full overflow-auto rounded-md border border-gray-400 bg-base-100"
>
<ComboboxOption
v-for="item in computedItems"
@@ -34,7 +34,7 @@
>
<li
:class="[
'relative cursor-default select-none py-2 pl-3 pr-9 duration-75 ease-in-out transition-colors',
'relative cursor-default select-none py-2 pl-3 pr-9 transition-colors duration-75 ease-in-out',
active ? 'bg-primary text-primary-content' : 'text-base-content',
]"
>
@@ -45,11 +45,11 @@
<span
v-if="selected"
:class="[
'absolute inset-y-0 right-0 flex text-primary items-center pr-4',
'absolute inset-y-0 right-0 flex items-center pr-4 text-primary',
active ? 'text-primary-content' : 'bg-primary',
]"
>
<MdiCheck class="h-5 w-5" aria-hidden="true" />
<MdiCheck class="size-5" aria-hidden="true" />
</span>
</slot>
</li>

View File

@@ -4,28 +4,33 @@
<span class="label-text">{{ label }}</span>
</label>
<div class="dropdown dropdown-top sm:dropdown-end">
<div tabindex="0" class="w-full min-h-[48px] flex gap-2 p-4 flex-wrap border border-gray-400 rounded-lg">
<div tabindex="0" class="flex min-h-[48px] w-full flex-wrap gap-2 rounded-lg border border-gray-400 p-4">
<span v-for="itm in value" :key="name != '' ? itm[name] : itm" class="badge">
{{ name != "" ? itm[name] : itm }}
</span>
</div>
<ul
<div
tabindex="0"
style="display: inline"
class="dropdown-content mb-1 menu shadow border border-gray-400 rounded bg-base-100 w-full z-[9999] max-h-60 overflow-y-scroll"
class="dropdown-content menu z-[9999] mb-1 w-full rounded border border-gray-400 bg-base-100 shadow"
>
<li
v-for="(obj, idx) in items"
:key="idx"
:class="{
bordered: selected[idx],
}"
>
<button type="button" @click="toggle(idx)">
{{ name != "" ? obj[name] : obj }}
</button>
</li>
</ul>
<div class="m-2">
<input v-model="search" placeholder="Search…" class="input input-bordered input-sm w-full" />
</div>
<ul class="max-h-60 overflow-y-scroll">
<li
v-for="(obj, idx) in filteredItems"
:key="idx"
:class="{
bordered: selected.includes(obj[props.uniqueField]),
}"
>
<button type="button" @click="toggle(obj[props.uniqueField])">
{{ name != "" ? obj[name] : obj }}
</button>
</li>
</ul>
</div>
</div>
</div>
</template>
@@ -49,6 +54,10 @@
type: String,
default: "name",
},
uniqueField: {
type: String,
default: "id",
},
selectFirst: {
type: Boolean,
default: false,
@@ -57,19 +66,26 @@
const value = useVModel(props, "modelValue", emit);
const selected = computed<Record<number, boolean>>(() => {
const obj: Record<number, boolean> = {};
value.value.forEach(itm => {
const idx = props.items.findIndex(item => item[props.name] === itm.name);
obj[idx] = true;
const search = ref("");
const filteredItems = computed(() => {
if (!search.value) {
return props.items;
}
return props.items.filter(item => {
return item[props.name].toLowerCase().includes(search.value.toLowerCase());
});
return obj;
});
function toggle(index: number) {
const item = props.items[index];
if (selected.value[index]) {
value.value = value.value.filter(itm => itm.name !== item.name);
const selected = computed<string[]>(() => {
return value.value.map(itm => itm[props.uniqueField]);
});
function toggle(uniqueField: string) {
const item = props.items.find(itm => itm[props.uniqueField] === uniqueField);
if (selected.value.includes(item[props.uniqueField])) {
value.value = value.value.filter(itm => itm[props.uniqueField] !== item[props.uniqueField]);
} else {
value.value = [...value.value, item];
}

View File

@@ -3,11 +3,11 @@
<FormTextField v-model="value" placeholder="Password" :label="label" :type="inputType"> </FormTextField>
<button
type="button"
class="inline-flex p-1 ml-1 justify-center mt-auto mb-3 tooltip absolute top-11 right-3"
class="tooltip absolute right-3 top-11 mb-3 ml-1 mt-auto inline-flex justify-center p-1"
data-tip="Toggle Password Show"
@click="toggle()"
>
<MdiEye name="mdi-eye" class="h-5 w-5" />
<MdiEye name="mdi-eye" class="size-5" />
</button>
</div>
</template>

View File

@@ -3,7 +3,7 @@
<label class="label">
<span class="label-text">{{ label }}</span>
</label>
<textarea ref="el" v-model="value" class="textarea w-full textarea-bordered h-28" :placeholder="placeholder" />
<textarea ref="el" v-model="value" class="textarea textarea-bordered h-28 w-full" :placeholder="placeholder" />
<label v-if="limit" class="label">
<span class="label-text-alt"></span>
<span class="label-text-alt"> {{ valueLen }}/{{ limit }}</span>
@@ -16,7 +16,7 @@
<textarea
ref="el"
v-model="value"
class="textarea textarea-bordered w-full col-span-3 mt-3 h-28"
class="textarea textarea-bordered col-span-3 mt-3 h-28 w-full"
auto-grow
:placeholder="placeholder"
auto-height

View File

@@ -9,7 +9,7 @@
<label class="label">
<span class="label-text">{{ label }}</span>
</label>
<input v-model="value" :placeholder="placeholder" class="input input-bordered col-span-3 w-full mt-2" />
<input v-model="value" :placeholder="placeholder" class="input input-bordered col-span-3 mt-2 w-full" />
</div>
</template>

View File

@@ -6,15 +6,15 @@
class="flex items-center justify-between py-3 pl-3 pr-4 text-sm"
>
<div class="flex w-0 flex-1 items-center">
<MdiPaperclip class="h-5 w-5 flex-shrink-0 text-gray-400" aria-hidden="true" />
<MdiPaperclip class="size-5 shrink-0 text-gray-400" aria-hidden="true" />
<span class="ml-2 w-0 flex-1 truncate"> {{ attachment.document.title }}</span>
</div>
<div class="ml-4 flex-shrink-0">
<div class="ml-4 shrink-0">
<a class="tooltip mr-2" data-tip="Download" :href="attachmentURL(attachment.id)" target="_blank">
<MdiDownload class="h-5 w-5" />
<MdiDownload class="size-5" />
</a>
<a class="tooltip" data-tip="Open" :href="attachmentURL(attachment.id)" target="_blank">
<MdiOpenInNew class="h-5 w-5" />
<MdiOpenInNew class="size-5" />
</a>
</div>
</li>

View File

@@ -1,32 +1,36 @@
<template>
<NuxtLink class="group card rounded-md border border-gray-300" :to="`/item/${item.id}`">
<div class="relative h-[200px]">
<img v-if="imageUrl" class="h-[200px] w-full object-cover rounded-t shadow-sm border-gray-300" :src="imageUrl" />
<img v-if="imageUrl" class="h-[200px] w-full rounded-t border-gray-300 object-cover shadow-sm" :src="imageUrl" />
<div class="absolute bottom-1 left-1">
<NuxtLink
v-if="item.location"
class="text-sm hover:link badge shadow-md rounded-md"
class="badge rounded-md text-sm shadow-md hover:link"
:to="`/location/${item.location.id}`"
>
{{ item.location.name }}
</NuxtLink>
</div>
</div>
<div class="rounded-b p-4 pt-2 flex-grow col-span-4 flex flex-col gap-y-1 bg-base-100">
<h2 class="text-lg font-bold two-line">{{ item.name }}</h2>
<div class="col-span-4 flex grow flex-col gap-y-1 rounded-b bg-base-100 p-4 pt-2">
<h2 class="line-clamp-2 text-ellipsis text-lg font-bold">{{ item.name }}</h2>
<div class="divider my-0"></div>
<div class="flex justify-between gap-2">
<div class="flex gap-2">
<div v-if="item.insured" class="tooltip z-10" data-tip="Insured">
<MdiShieldCheck class="h-5 w-5 text-primary" />
<MdiShieldCheck class="size-5 text-primary" />
</div>
<div v-if="item.archived" class="tooltip z-10" data-tip="Archived">
<MdiArchive class="size-5 text-red-700" />
</div>
<div class="grow"></div>
<div class="tooltip" data-tip="Quantity">
<span class="badge h-5 w-5 badge-primary badge-sm text-xs">
<span class="badge badge-primary badge-sm size-5 text-xs">
{{ item.quantity }}
</span>
</div>
</div>
<Markdown class="mb-2 text-clip three-line" :source="item.description" />
<div class="flex gap-2 flex-wrap -mr-1 mt-auto justify-end">
<Markdown class="mb-2 line-clamp-3 text-ellipsis" :source="item.description" />
<div class="-mr-1 mt-auto flex flex-wrap justify-end gap-2">
<LabelChip v-for="label in top3" :key="label.id" :label="label" size="sm" />
</div>
</div>
@@ -36,6 +40,7 @@
<script setup lang="ts">
import type { ItemOut, ItemSummary } from "~~/lib/api/types/data-contracts";
import MdiShieldCheck from "~icons/mdi/shield-check";
import MdiArchive from "~icons/mdi/archive";
const api = useUserApi();
@@ -59,22 +64,4 @@
});
</script>
<style lang="css">
.three-line {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
line-clamp: 3;
-webkit-box-orient: vertical;
}
.two-line {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>
<style lang="css"></style>

View File

@@ -1,34 +1,51 @@
<template>
<BaseModal v-model="modal">
<template #title> Create Item </template>
<template #title> {{ $t("components.item.create_modal.title") }} </template>
<form @submit.prevent="create()">
<LocationSelector v-model="form.location" />
<FormTextField ref="nameInput" v-model="form.name" :trigger-focus="focused" :autofocus="true" label="Item Name" />
<FormTextArea v-model="form.description" label="Item Description" />
<FormMultiselect v-model="form.labels" label="Labels" :items="labels ?? []" />
<div class="modal-action">
<div class="flex justify-center">
<div>
<label for="photo" class="btn">{{ $t("components.item.create_modal.photo_button") }}</label>
<input id="photo" type="file" accept="image/*" style="visibility: hidden" @change="previewImage" />
</div>
<BaseButton class="rounded-r-none" :loading="loading" type="submit">
<template #icon>
<MdiPackageVariant class="swap-off h-5 w-5" />
<MdiPackageVariantClosed class="swap-on h-5 w-5" />
<MdiPackageVariant class="swap-off size-5" />
<MdiPackageVariantClosed class="swap-on size-5" />
</template>
Create
{{ $t("global.create") }}
</BaseButton>
<div class="dropdown dropdown-top">
<label tabindex="0" class="btn rounded-l-none rounded-r-xl">
<MdiChevronDown class="h-5 w-5" name="mdi-chevron-down" />
<MdiChevronDown class="size-5" name="mdi-chevron-down" />
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-64 right-0">
<ul tabindex="0" class="dropdown-content menu rounded-box right-0 w-64 bg-base-100 p-2 shadow">
<li>
<button type="button" @click="create(false)">Create and Add Another</button>
<button type="button" @click="create(false)">{{ $t("global.create_and_add") }}</button>
</li>
</ul>
</div>
</div>
</div>
<!-- photo preview area is AFTER the create button, to avoid pushing the button below the screen on small displays -->
<div class="border-t border-gray-300 p-4">
<template v-if="form.preview">
<p class="mb-0">File name: {{ form.photo?.name }}</p>
<img
:src="form.preview"
class="h-[100px] w-full rounded-t border-gray-300 object-cover shadow-sm"
alt="Uploaded Photo"
/>
</template>
</div>
</form>
<p class="text-sm text-center mt-4">
<p class="mt-4 text-center text-sm">
use <kbd class="kbd kbd-xs">Shift</kbd> + <kbd class="kbd kbd-xs"> Enter </kbd> to create and add another
</p>
</BaseModal>
@@ -41,6 +58,7 @@
import MdiPackageVariant from "~icons/mdi/package-variant";
import MdiPackageVariantClosed from "~icons/mdi/package-variant-closed";
import MdiChevronDown from "~icons/mdi/chevron-down";
import { AttachmentTypes } from "~~/lib/api/types/non-generated";
const props = defineProps({
modelValue: {
@@ -85,10 +103,25 @@
description: "",
color: "", // Future!
labels: [] as LabelOut[],
preview: null as string | null,
photo: null as File | null,
});
const { shift } = useMagicKeys();
function previewImage(event: Event) {
const input = event.target as HTMLInputElement;
if (input.files && input.files.length > 0) {
const reader = new FileReader();
reader.onload = e => {
form.preview = e.target?.result as string;
};
const file = input.files[0];
form.photo = file;
reader.readAsDataURL(file);
}
}
whenever(
() => modal.value,
() => {
@@ -112,6 +145,13 @@
return;
}
if (loading.value) {
toast.error("Already creating an item");
return;
}
loading.value = true;
if (shift.value) {
close = false;
}
@@ -127,16 +167,32 @@
const { error, data } = await api.items.create(out);
loading.value = false;
if (error) {
loading.value = false;
toast.error("Couldn't create item");
return;
}
toast.success("Item created");
// if the photo was provided, upload it
if (form.photo) {
const { error } = await api.items.attachments.add(data.id, form.photo, form.photo.name, AttachmentTypes.Photo);
if (error) {
loading.value = false;
toast.error("Failed to upload Photo");
return;
}
toast.success("Photo uploaded");
}
// Reset
form.name = "";
form.description = "";
form.color = "";
form.preview = null;
form.photo = null;
focused.value = false;
loading.value = false;

View File

@@ -28,24 +28,24 @@
<template>
<section>
<BaseSectionHeader class="mb-2 flex justify-between items-center">
Items
<BaseSectionHeader class="mb-2 flex items-center justify-between">
{{ $t("components.item.view.selectable.items") }}
<template #description>
<div v-if="!viewSet" class="dropdown dropdown-hover dropdown-left">
<div v-if="!viewSet" class="dropdown dropdown-left dropdown-hover">
<label tabindex="0" class="btn btn-ghost m-1">
<MdiDotsVertical class="h-7 w-7" />
<MdiDotsVertical class="size-7" />
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-32">
<ul tabindex="0" class="dropdown-content menu rounded-box w-32 bg-base-100 p-2 shadow">
<li>
<button @click="setViewPreference('card')">
<MdiCardTextOutline class="h-5 w-5" />
Card
<MdiCardTextOutline class="size-5" />
{{ $t("components.item.view.selectable.card") }}
</button>
</li>
<li>
<button @click="setViewPreference('table')">
<MdiTable class="h-5 w-5" />
Table
<MdiTable class="size-5" />
{{ $t("components.item.view.selectable.table") }}
</button>
</li>
</ul>
@@ -57,9 +57,9 @@
<ItemViewTable :items="items" />
</template>
<template v-else>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
<ItemCard v-for="item in items" :key="item.id" :item="item" />
<div class="first:block hidden text-lg">No Items to Display</div>
<div class="hidden text-lg first:block">{{ $t("components.item.view.selectable.no_items") }}</div>
</div>
</template>
</section>

View File

@@ -5,6 +5,8 @@ export type TableHeader = {
value: keyof ItemSummary;
sortable?: boolean;
align?: "left" | "center" | "right";
enabled: boolean;
type?: "price" | "boolean" | "name" | "location" | "date";
};
export type TableData = Record<string, any>;

View File

@@ -4,9 +4,9 @@
<thead>
<tr>
<th
v-for="h in headers"
v-for="h in headers.filter(h => h.enabled)"
:key="h.value"
class="text-no-transform text-sm bg-neutral text-neutral-content cursor-pointer"
class="text-no-transform cursor-pointer bg-neutral text-sm text-neutral-content"
@click="sortBy(h.value)"
>
<div
@@ -24,8 +24,8 @@
:class="`inline-flex ${sortByProperty === h.value ? '' : 'opacity-0'}`"
>
<span class="swap swap-rotate" :class="{ 'swap-active': pagination.descending }">
<MdiArrowDown class="swap-on h-5 w-5" />
<MdiArrowUp class="swap-off h-5 w-5" />
<MdiArrowDown class="swap-on size-5" />
<MdiArrowUp class="swap-off size-5" />
</span>
</div>
</div>
@@ -35,7 +35,7 @@
<tbody>
<tr v-for="(d, i) in data" :key="d.id" class="hover cursor-pointer" @click="navigateTo(`/item/${d.id}`)">
<td
v-for="h in headers"
v-for="h in headers.filter(h => h.enabled)"
:key="`${h.value}-${i}`"
class="bg-base-100"
:class="{
@@ -44,17 +44,25 @@
'text-left': h.align === 'left',
}"
>
<template v-if="cell(h) === 'cell-name'">
<template v-if="h.type === 'name'">
<NuxtLink class="hover" :to="`/item/${d.id}`">
{{ d.name }}
</NuxtLink>
</template>
<template v-else-if="cell(h) === 'cell-purchasePrice'">
<template v-else-if="h.type === 'price'">
<Currency :amount="d.purchasePrice" />
</template>
<template v-else-if="cell(h) === 'cell-insured'">
<MdiCheck v-if="d.insured" class="text-green-500 h-5 w-5 inline" />
<MdiClose v-else class="text-red-500 h-5 w-5 inline" />
<template v-else-if="h.type === 'boolean'">
<MdiCheck v-if="d.insured" class="inline size-5 text-green-500" />
<MdiClose v-else class="inline size-5 text-red-500" />
</template>
<template v-else-if="h.type === 'location'">
<NuxtLink v-if="d.location" class="hover:link" :to="`/location/${d.location.id}`">
{{ d.location.name }}
</NuxtLink>
</template>
<template v-else-if="h.type === 'date'">
<DateTime :date="d[h.value]" datetime-type="date" />
</template>
<slot v-else :name="cell(h)" v-bind="{ item: d }">
{{ extractValue(d, h.value) }}
@@ -63,7 +71,55 @@
</tr>
</tbody>
</table>
<div v-if="hasPrev || hasNext" class="border-t p-3 justify-end flex">
<div
class="flex items-center justify-end gap-3 border-t p-3"
:class="{
hidden: disableControls,
}"
>
<div class="dropdown dropdown-top dropdown-hover">
<label tabindex="0" class="btn btn-square btn-outline btn-sm m-1">
<MdiTableCog />
</label>
<ul tabindex="0" class="dropdown-content rounded-box flex w-64 flex-col gap-2 bg-base-100 p-2 pl-3 shadow">
<li>Headers:</li>
<li v-for="(h, i) in headers" class="flex flex-row items-center gap-1">
<button
class="btn btn-square btn-ghost btn-xs"
:class="{
'btn-disabled': i === 0,
}"
@click="moveHeader(i, i - 1)"
>
<MdiArrowUp />
</button>
<button
class="btn btn-square btn-ghost btn-xs"
:class="{
'btn-disabled': i === headers.length - 1,
}"
@click="moveHeader(i, i + 1)"
>
<MdiArrowDown />
</button>
<input
:id="h.value"
type="checkbox"
class="checkbox checkbox-primary"
:checked="h.enabled"
@change="toggleHeader(h.value)"
/>
<label class="label-text" :for="h.value"> {{ h.text }} </label>
</li>
</ul>
</div>
<div class="hidden md:block">Rows per page</div>
<select v-model.number="pagination.rowsPerPage" class="select select-primary select-sm">
<option :value="10">10</option>
<option :value="25">25</option>
<option :value="50">50</option>
<option :value="100">100</option>
</select>
<div class="btn-group">
<button :disabled="!hasPrev" class="btn btn-sm" @click="prev()">«</button>
<button class="btn btn-sm">Page {{ pagination.page }}</button>
@@ -80,30 +136,67 @@
import MdiArrowUp from "~icons/mdi/arrow-up";
import MdiCheck from "~icons/mdi/check";
import MdiClose from "~icons/mdi/close";
import MdiTableCog from "~icons/mdi/table-cog";
type Props = {
items: ItemSummary[];
disableControls?: boolean;
};
const props = defineProps<Props>();
const sortByProperty = ref<keyof ItemSummary | "">("");
const headers = computed<TableHeader[]>(() => {
return [
{ text: "Name", value: "name" },
{ text: "Quantity", value: "quantity", align: "center" },
{ text: "Insured", value: "insured", align: "center" },
{ text: "Price", value: "purchasePrice" },
] as TableHeader[];
});
const preferences = useViewPreferences();
const defaultHeaders = [
{ text: "Name", value: "name", enabled: true, type: "name" },
{ text: "Quantity", value: "quantity", align: "center", enabled: true },
{ text: "Insured", value: "insured", align: "center", enabled: true, type: "boolean" },
{ text: "Price", value: "purchasePrice", align: "center", enabled: true, type: "price" },
{ text: "Location", value: "location", align: "center", enabled: false, type: "location" },
{ text: "Archived", value: "archived", align: "center", enabled: false, type: "boolean" },
{ text: "Created At", value: "createdAt", align: "center", enabled: false, type: "date" },
{ text: "Updated At", value: "updatedAt", align: "center", enabled: false, type: "date" },
] satisfies TableHeader[];
const headers = ref<TableHeader[]>(
(preferences.value.tableHeaders ?? []).concat(
defaultHeaders.filter(h => !preferences.value.tableHeaders?.find(h2 => h2.value === h.value))
)
);
console.log(headers.value);
const toggleHeader = (value: string) => {
const header = headers.value.find(h => h.value === value);
if (header) {
header.enabled = !header.enabled; // Toggle the 'enabled' state
}
preferences.value.tableHeaders = headers.value;
};
const moveHeader = (from: number, to: number) => {
const header = headers.value[from];
headers.value.splice(from, 1);
headers.value.splice(to, 0, header);
preferences.value.tableHeaders = headers.value;
};
const pagination = reactive({
descending: false,
page: 1,
rowsPerPage: 10,
rowsPerPage: preferences.value.itemsPerTablePage,
rowsNumber: 0,
});
watch(
() => pagination.rowsPerPage,
newRowsPerPage => {
preferences.value.itemsPerTablePage = newRowsPerPage;
}
);
const next = () => pagination.page++;
const hasNext = computed<boolean>(() => {
return pagination.page * pagination.rowsPerPage < props.items.length;
@@ -189,4 +282,20 @@
}
</script>
<style scoped></style>
<style scoped>
:where(.table *:first-child) :where(*:first-child) :where(th, td):first-child {
border-top-left-radius: 0.5rem;
}
:where(.table *:first-child) :where(*:first-child) :where(th, td):last-child {
border-top-right-radius: 0.5rem;
}
:where(.table *:last-child) :where(*:last-child) :where(th, td):first-child {
border-bottom-left-radius: 0.5rem;
}
:where(.table *:last-child) :where(*:last-child) :where(th, td):last-child {
border-bottom-right-radius: 0.5rem;
}
</style>

View File

@@ -29,13 +29,13 @@
:class="{
'badge-lg p-4': size === 'lg',
'p-3': size !== 'sm' && size !== 'lg',
'p-2 badge-sm': size === 'sm',
'badge-sm p-2': size === 'sm',
}"
:to="`/label/${label.id}`"
>
<label class="swap swap-rotate" :class="isActive ? 'swap-active' : ''">
<MdiArrowRight class="mr-2 swap-on" />
<MdiTagOutline class="mr-2 swap-off" />
<MdiArrowRight class="swap-on mr-2" />
<MdiTagOutline class="swap-off mr-2" />
</label>
{{ label.name }}
</NuxtLink>

View File

@@ -1,6 +1,6 @@
<template>
<BaseModal v-model="modal">
<template #title> Create Label </template>
<template #title>{{ $t("components.label.create_modal.title") }}</template>
<form @submit.prevent="create()">
<FormTextField
ref="locationNameRef"
@@ -12,21 +12,21 @@
<FormTextArea v-model="form.description" label="Label Description" />
<div class="modal-action">
<div class="flex justify-center">
<BaseButton class="rounded-r-none" :loading="loading" type="submit"> Create </BaseButton>
<BaseButton class="rounded-r-none" :loading="loading" type="submit"> {{ $t("global.create") }} </BaseButton>
<div class="dropdown dropdown-top">
<label tabindex="0" class="btn rounded-l-none rounded-r-xl">
<MdiChevronDown class="h-5 w-5" />
<MdiChevronDown class="size-5" />
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-64 right-0">
<ul tabindex="0" class="dropdown-content menu rounded-box right-0 w-64 bg-base-100 p-2 shadow">
<li>
<button type="button" @click="create(false)">Create and Add Another</button>
<button type="button" @click="create(false)">{{ $t("global.create_and_add") }}</button>
</li>
</ul>
</div>
</div>
</div>
</form>
<p class="text-sm text-center mt-4">
<p class="mt-4 text-center text-sm">
use <kbd class="kbd kbd-xs">Shift</kbd> + <kbd class="kbd kbd-xs"> Enter </kbd> to create and add another
</p>
</BaseModal>
@@ -71,6 +71,12 @@
const { shift } = useMagicKeys();
async function create(close = true) {
if (loading.value) {
toast.error("Already creating a label");
return;
}
loading.value = true;
if (shift.value) {
close = false;
}

View File

@@ -2,24 +2,24 @@
<NuxtLink
ref="card"
:to="`/location/${location.id}`"
class="card bg-base-100 text-base-content rounded-md transition duration-300 shadow-md"
class="card rounded-md bg-base-100 text-base-content shadow-md transition duration-300"
>
<div
class="card-body"
:class="{
'p-4': !dense,
'py-2 px-3': dense,
'px-3 py-2': dense,
}"
>
<h2 class="flex items-center justify-between gap-2">
<label class="swap swap-rotate" :class="isActive ? 'swap-active' : ''">
<MdiArrowRight class="swap-on h-6 w-6" />
<MdiMapMarkerOutline class="swap-off h-6 w-6" />
<MdiArrowRight class="swap-on size-6" />
<MdiMapMarkerOutline class="swap-off size-6" />
</label>
<span class="mx-auto">
{{ location.name }}
</span>
<span class="badge badge-primary h-6 badge-lg" :class="{ 'opacity-0': !hasCount }">
<span class="badge badge-primary badge-lg h-6" :class="{ 'opacity-0': !hasCount }">
{{ count }}
</span>
</h2>

View File

@@ -1,6 +1,6 @@
<template>
<BaseModal v-model="modal">
<template #title> Create Location </template>
<template #title>{{ $t("components.location.create_modal.title") }}</template>
<form @submit.prevent="create()">
<FormTextField
ref="locationNameRef"
@@ -13,21 +13,21 @@
<LocationSelector v-model="form.parent" />
<div class="modal-action">
<div class="flex justify-center">
<BaseButton class="rounded-r-none" type="submit" :loading="loading"> Create </BaseButton>
<BaseButton class="rounded-r-none" type="submit" :loading="loading">{{ $t("global.create") }}</BaseButton>
<div class="dropdown dropdown-top">
<label tabindex="0" class="btn rounded-l-none rounded-r-xl">
<MdiChevronDown class="h-5 w-5" />
<MdiChevronDown class="size-5" />
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-64 right-0">
<ul tabindex="0" class="dropdown-content menu rounded-box right-0 w-64 bg-base-100 p-2 shadow">
<li>
<button type="button" @click="create(false)">Create and Add Another</button>
<button type="button" @click="create(false)">{{ $t("global.create_and_add") }}</button>
</li>
</ul>
</div>
</div>
</div>
</form>
<p class="text-sm text-center mt-4">
<p class="mt-4 text-center text-sm">
use <kbd class="kbd kbd-xs">Shift</kbd> + <kbd class="kbd kbd-xs"> Enter </kbd> to create and add another
</p>
</BaseModal>
@@ -73,6 +73,10 @@
const { shift } = useMagicKeys();
async function create(close = true) {
if (loading.value) {
toast.error("Already creating a location");
return;
}
loading.value = true;
if (shift.value) {

View File

@@ -8,10 +8,10 @@
v-if="selected"
:class="['absolute inset-y-0 right-0 flex items-center pr-4', active ? 'text-white' : 'text-primary']"
>
<MdiCheck class="h-5 w-5" aria-hidden="true" />
<MdiCheck class="size-5" aria-hidden="true" />
</span>
</div>
<div v-if="cast(item.value).name != cast(item.value).treeString" class="text-xs mt-1">
<div v-if="cast(item.value).name != cast(item.value).treeString" class="mt-1 text-xs">
{{ cast(item.value).treeString }}
</div>
</div>

View File

@@ -40,19 +40,19 @@
<template>
<div>
<div
class="node flex items-center gap-1 rounded p-1"
class="flex items-center gap-1 rounded p-1"
:class="{
'cursor-pointer hover:bg-base-200': hasChildren,
}"
@click="openRef = !openRef"
>
<div
class="p-1/2 rounded mr-1 flex items-center justify-center"
class="mr-1 flex items-center justify-center rounded p-0.5"
:class="{
'hover:bg-base-200': hasChildren,
}"
>
<div v-if="!hasChildren" class="h-6 w-6"></div>
<div v-if="!hasChildren" class="size-6"></div>
<label
v-else
class="swap swap-rotate"
@@ -60,13 +60,13 @@
'swap-active': openRef,
}"
>
<MdiChevronRight name="mdi-chevron-right" class="h-6 w-6 swap-off" />
<MdiChevronDown name="mdi-chevron-down" class="h-6 w-6 swap-on" />
<MdiChevronRight name="mdi-chevron-right" class="swap-off size-6" />
<MdiChevronDown name="mdi-chevron-down" class="swap-on size-6" />
</label>
</div>
<MdiMapMarker v-if="item.type === 'location'" class="h-4 w-4" />
<MdiPackageVariant v-else class="h-4 w-4" />
<NuxtLink class="hover:link text-lg" :to="link" @click.stop>{{ item.name }} </NuxtLink>
<MdiMapMarker v-if="item.type === 'location'" class="size-4" />
<MdiPackageVariant v-else class="size-4" />
<NuxtLink class="text-lg hover:link" :to="link" @click.stop>{{ item.name }} </NuxtLink>
</div>
<div v-if="openRef" class="ml-4">
<LocationTreeNode v-for="child in item.children" :key="child.id" :item="child" :tree-id="treeId" />

View File

@@ -10,7 +10,7 @@
</script>
<template>
<div class="p-4 border-2 root">
<div class="root border-2 p-4">
<LocationTreeNode v-for="item in locs" :key="item.id" :item="item" :tree-id="treeId" />
</div>
</template>

View File

@@ -1,11 +1,11 @@
<template>
<BaseModal v-model="isRevealed" readonly @cancel="cancel(false)">
<template #title> Confirm </template>
<template #title> {{ $t("global.confirm") }} </template>
<div>
<p>{{ text }}</p>
</div>
<div class="modal-action">
<BaseButton type="submit" @click="confirm(true)"> Confirm </BaseButton>
<BaseButton type="submit" @click="confirm(true)"> {{ $t("global.confirm") }} </BaseButton>
</div>
</BaseModal>
</template>

View File

@@ -1,37 +1,37 @@
<template>
<div ref="el" class="dropdown" :class="{ 'dropdown-open': dropdownOpen }">
<button ref="btn" tabindex="0" class="btn btn-xs" @click="toggle">
{{ label }} {{ len }} <MdiChevronDown class="h-4 w-4" />
{{ label }} {{ len }} <MdiChevronDown class="size-4" />
</button>
<div tabindex="0" class="dropdown-content mt-1 w-64 shadow bg-base-100 rounded-md">
<div class="pt-4 px-4 shadow-sm mb-1">
<input v-model="search" type="text" placeholder="Search…" class="input input-sm input-bordered w-full mb-2" />
<div tabindex="0" class="dropdown-content mt-1 w-64 rounded-md bg-base-100 shadow">
<div class="mb-1 px-4 pt-4 shadow-sm">
<input v-model="search" type="text" placeholder="Search…" class="input input-bordered input-sm mb-2 w-full" />
</div>
<div class="overflow-y-auto max-h-72 divide-y">
<div class="max-h-72 divide-y overflow-y-auto">
<label
v-for="v in selectedView"
:key="v"
class="cursor-pointer px-4 label flex justify-between hover:bg-base-200"
class="label flex cursor-pointer justify-between px-4 hover:bg-base-200"
>
<span class="label-text mr-2">
<slot name="display" v-bind="{ item: v }">
{{ v[display] }}
</slot>
</span>
<input v-model="selected" type="checkbox" :value="v" class="checkbox checkbox-sm checkbox-primary" />
<input v-model="selected" type="checkbox" :value="v" class="checkbox checkbox-primary checkbox-sm" />
</label>
<hr v-if="selected.length > 0" />
<label
v-for="v in unselected"
:key="v"
class="cursor-pointer px-4 label flex justify-between hover:bg-base-200"
class="label flex cursor-pointer justify-between px-4 hover:bg-base-200"
>
<span class="label-text mr-2">
<slot name="display" v-bind="{ item: v }">
{{ v[display] }}
</slot>
</span>
<input v-model="selected" type="checkbox" :value="v" class="checkbox checkbox-sm checkbox-primary" />
<input v-model="selected" type="checkbox" :value="v" class="checkbox checkbox-primary checkbox-sm" />
</label>
</div>
</div>
@@ -45,6 +45,7 @@
options: any[];
display?: string;
modelValue: any[];
uniqueField: string;
};
const btn = ref<HTMLButtonElement>();
@@ -75,6 +76,7 @@
label: "",
display: "name",
modelValue: () => [],
uniqueField: "id",
});
const len = computed(() => {
@@ -95,9 +97,12 @@
const unselected = computed(() => {
return props.options.filter(o => {
if (searchFold.value.length > 0) {
return o[props.display].toLowerCase().includes(searchFold.value) && !selected.value.includes(o);
return (
o[props.display].toLowerCase().includes(searchFold.value) &&
selected.value.every(s => s[props.uniqueField] !== o[props.uniqueField])
);
}
return !selected.value.includes(o);
return selected.value.every(s => s[props.uniqueField] !== o[props.uniqueField]);
});
});
</script>

View File

@@ -1,11 +1,11 @@
<template>
<div class="border-t border-gray-300 px-4 py-5 sm:p-0">
<dl class="sm:divide-y sm:divide-gray-300">
<div v-for="(detail, i) in details" :key="i" class="py-4 sm:grid group sm:grid-cols-3 sm:gap-4 sm:px-6">
<div v-for="(detail, i) in details" :key="i" class="group py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-base-content">
{{ detail.name }}
</dt>
<dd class="text-sm text-base-content text-start sm:col-span-2">
<dd class="text-start text-sm text-base-content sm:col-span-2">
<slot :name="detail.slot || detail.name" v-bind="{ detail }">
<DateTime
v-if="detail.type == 'date'"
@@ -14,9 +14,9 @@
/>
<Currency v-else-if="detail.type == 'currency'" :amount="detail.text" />
<template v-else-if="detail.type === 'link'">
<div class="tooltip tooltip-primary tooltip-top" :data-tip="detail.href">
<div class="tooltip tooltip-top tooltip-primary" :data-tip="detail.href">
<a class="btn btn-primary btn-xs" :href="detail.href" target="_blank">
<MdiOpenInNew class="mr-2 swap-on" />
<MdiOpenInNew class="swap-on mr-2" />
{{ detail.text }}
</a>
</div>
@@ -31,13 +31,13 @@
{{ detail.text }}
<span
v-if="detail.copyable"
class="opacity-0 group-hover:opacity-100 ml-4 my-0 duration-75 transition-opacity"
class="my-0 ml-4 opacity-0 transition-opacity duration-75 group-hover:opacity-100"
>
<CopyText
v-if="detail.text.toString()"
:text="detail.text.toString()"
:icon-size="16"
class="btn btn-xs btn-ghost btn-circle"
class="btn btn-circle btn-ghost btn-xs"
/>
</span>
</span>

View File

@@ -1,7 +1,7 @@
<template>
<div
ref="el"
class="h-24 w-full border-2 border-primary border-dashed grid place-content-center"
class="grid h-24 w-full place-content-center border-2 border-dashed border-primary"
:class="isOverDropZone ? 'bg-primary bg-opacity-10' : ''"
>
<slot />

View File

@@ -5,9 +5,9 @@
<MdiQrcode />
</label>
</slot>
<div tabindex="0" class="card compact dropdown-content shadow-lg bg-base-100 rounded-box w-64">
<div tabindex="0" class="card dropdown-content compact rounded-box w-64 bg-base-100 shadow-lg">
<div class="card-body">
<h2 class="text-center">Page URL</h2>
<h2 class="text-center">{{ $t("components.global.page_qr_code.page_url") }}</h2>
<img :src="getQRCodeUrl()" />
</div>
</div>

View File

@@ -1,8 +1,8 @@
<template>
<div class="py-4">
<p class="text-sm">Password Strength: {{ message }}</p>
<p class="text-sm">{{ $t("components.global.password_score.password_strength") }}: {{ message }}</p>
<progress
class="progress w-full progress-bar"
class="progress w-full"
:value="score"
max="100"
:class="{

View File

@@ -1,3 +1,3 @@
<template>
<div class="grow-1 max-w-full"></div>
<div class="max-w-full"></div>
</template>

View File

@@ -1,7 +1,7 @@
<template>
<div class="stats bg-neutral shadow rounded-md">
<div class="stat text-neutral-content text-center space-y-1 p-3">
<div class="stat-title">{{ title }}</div>
<div class="stats rounded-md bg-neutral shadow">
<div class="stat space-y-1 p-3 text-center text-neutral-content">
<div class="stat-title text-neutral-content">{{ title }}</div>
<div class="stat-value text-2xl">
<Currency v-if="type === 'currency'" :amount="value" />
<template v-if="type === 'number'">{{ value }}</template>
@@ -27,8 +27,4 @@
});
</script>
<style>
[data-theme="homebox"] .stat-title {
color: hsl(0 0% 90/0.6);
}
</style>
<style></style>

View File

@@ -1,5 +1,5 @@
<template>
<h3 class="flex gap-2 items-center mb-3 pl-1 text-lg">
<h3 class="mb-3 flex items-center gap-2 pl-1 text-lg">
<slot />
</h3>
</template>

View File

@@ -6,7 +6,7 @@
<th
v-for="h in headers"
:key="h.value"
class="text-no-transform text-sm bg-neutral text-neutral-content"
class="text-no-transform bg-neutral text-sm text-neutral-content"
:class="{
'text-center': h.align === 'center',
'text-right': h.align === 'right',

View File

@@ -1,4 +1,5 @@
import type { Ref } from "vue";
import type { TableHeader } from "components/Item/View/Table.types";
import type { DaisyTheme } from "~~/lib/data/themes";
export type ViewType = "table" | "card" | "tree";
@@ -9,6 +10,10 @@ export type LocationViewPreferences = {
editorAdvancedView: boolean;
itemDisplayView: ViewType;
theme: DaisyTheme;
itemsPerTablePage: number;
tableHeaders?: TableHeader[];
displayHeaderDecor: boolean;
language?: string;
};
/**
@@ -24,6 +29,9 @@ export function useViewPreferences(): Ref<LocationViewPreferences> {
editorAdvancedView: false,
itemDisplayView: "card",
theme: "homebox",
itemsPerTablePage: 10,
displayHeaderDecor: true,
language: null,
},
{ mergeDefaults: true }
);

View File

@@ -32,7 +32,7 @@ export function useRouteQuery(q: string, def: any): WritableComputedRef<any> {
case "string":
return computed({
get: () => {
const qv = route.query[q];
const qv = first.value;
if (Array.isArray(qv)) {
return qv[0];
}

View File

@@ -1,5 +1,5 @@
<template>
<main class="w-full min-h-screen bg-blue-100 grid place-items-center">
<main class="grid min-h-screen w-full place-items-center bg-blue-100">
<slot></slot>
</main>
</template>

View File

@@ -13,22 +13,28 @@
<div class="drawer drawer-mobile">
<input id="my-drawer-2" v-model="drawerToggle" type="checkbox" class="drawer-toggle" />
<div class="drawer-content justify-center bg-base-300 pt-20 lg:pt-0">
<AppHeaderDecor class="-mt-10 hidden lg:block" />
<AppHeaderDecor v-if="preferences.displayHeaderDecor" class="-mt-10 hidden lg:block" />
<!-- Button -->
<div class="navbar z-[99] lg:hidden top-0 fixed bg-primary shadow-md drawer-button">
<label for="my-drawer-2" class="btn btn-square btn-ghost text-base-100 drawer-button lg:hidden">
<MdiMenu class="h-6 w-6" />
<div class="navbar drawer-button fixed top-0 z-[99] bg-primary shadow-md lg:hidden">
<label for="my-drawer-2" class="btn btn-square btn-ghost drawer-button text-base-100 lg:hidden">
<MdiMenu class="size-6" />
</label>
<NuxtLink to="/home">
<h2 class="text-3xl font-bold tracking-tight text-base-100 flex">
<h2 class="flex text-3xl font-bold tracking-tight text-base-100">
HomeB
<AppLogo class="w-8 -mb-3" />
<AppLogo class="-mb-3 w-8" />
x
</h2>
</NuxtLink>
</div>
<slot></slot>
<footer v-if="status" class="bottom-0 w-full bg-base-300 pb-4 text-center text-secondary-content">
<p class="text-center text-sm">
{{ $t("global.version", { version: status.build.version }) }} ~
{{ $t("global.build", { build: status.build.commit }) }}
</p>
</footer>
</div>
<!-- Sidebar -->
@@ -36,26 +42,26 @@
<label for="my-drawer-2" class="drawer-overlay"></label>
<!-- Top Section -->
<div class="w-60 py-5 md:py-10 bg-base-200 flex flex-grow-1 flex-col">
<div class="flex w-60 flex-col bg-base-200 py-5 md:py-10">
<div class="space-y-8">
<div class="flex flex-col items-center gap-4">
<p>Welcome, {{ username }}</p>
<p>{{ $t("global.welcome", { username: username }) }}</p>
<NuxtLink class="avatar placeholder" to="/home">
<div class="bg-base-300 text-neutral-content rounded-full w-24 p-4">
<div class="w-24 rounded-full bg-base-300 p-4 text-neutral-content">
<AppLogo />
</div>
</NuxtLink>
</div>
<div class="flex flex-col bg-base-200">
<div class="mx-auto w-40 mb-6">
<div class="dropdown overflow visible w-40">
<label tabindex="0" class="btn btn-primary btn-block text-lg text-no-transform">
<div class="mx-auto mb-6 w-40">
<div class="dropdown visible w-40">
<label tabindex="0" class="text-no-transform btn btn-primary btn-block text-lg">
<span>
<MdiPlus class="mr-1 -ml-1" />
<MdiPlus class="-ml-1 mr-1" />
</span>
Create
{{ $t("global.create") }}
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40">
<ul tabindex="0" class="dropdown-content menu rounded-box w-40 bg-base-100 p-2 shadow">
<li v-for="btn in dropdown" :key="btn.name">
<button @click="btn.action">
{{ btn.name }}
@@ -64,7 +70,7 @@
</ul>
</div>
</div>
<ul class="flex flex-col mx-auto gap-2 w-40 menu">
<ul class="menu mx-auto flex w-40 flex-col gap-2">
<li v-for="n in nav" :key="n.id" class="text-xl" @click="unfocus">
<NuxtLink
v-if="n.to"
@@ -74,7 +80,7 @@
'bg-secondary text-secondary-content': n.active?.value,
}"
>
<component :is="n.icon" class="h-6 w-6 mr-4" />
<component :is="n.icon" class="mr-4 size-6" />
{{ n.name }}
</NuxtLink>
</li>
@@ -83,7 +89,9 @@
</div>
<!-- Bottom -->
<button class="mt-auto mx-2 hover:bg-base-300 p-3 rounded-btn" @click="logout">Sign Out</button>
<button class="rounded-btn mx-2 mt-auto p-3 hover:bg-base-300" @click="logout">
{{ $t("global.sign_out") }}
</button>
</div>
</div>
</div>
@@ -101,12 +109,19 @@
import MdiMagnify from "~icons/mdi/magnify";
import MdiAccount from "~icons/mdi/account";
import MdiCog from "~icons/mdi/cog";
const username = computed(() => authCtx.user?.name || "User");
const preferences = useViewPreferences();
const pubApi = usePublicApi();
const { data: status } = useAsyncData(async () => {
const { data } = await pubApi.status();
return data;
});
// Preload currency format
useFormatCurrency();
const modals = reactive({
item: false,
location: false,

128
frontend/locales/ca.json Normal file
View File

@@ -0,0 +1,128 @@
{
"components": {
"app": {
"import_dialog": {
"title": "Importa un fitxer CSV",
"upload": "Puja",
"description": "Importa un fitxer CSV que contingui els articles, etiquetes i ubicacions. \nConsulteu la documentació per a més informació sobre el format requerit.",
"change_warning": "El comportament de les importacions amb import_refs existents ha canviat. Si hi ha un import_refs al fitxer CSV, \nl'article s'actualitzarà amb els valors del fitxer CSV."
}
},
"item": {
"create_modal": {
"title": "Crea un article",
"photo_button": "Foto 📷"
},
"view": {
"selectable": {
"card": "Targeta",
"items": "Articles",
"no_items": "No hi ha articles a mostrar",
"table": "Taula"
}
}
},
"label": {
"create_modal": {
"title": "Crea una etiqueta"
}
},
"location": {
"create_modal": {
"title": "Crea una ubicació"
}
},
"global": {
"page_qr_code": {
"page_url": "URL de la pàgina"
},
"password_score": {
"password_strength": "Força de la contrasenya"
}
}
},
"global": {
"build": "Construcció: { build }",
"confirm": "Confirma",
"create": "Crea",
"create_and_add": "Crea i afegeix-ne un altre",
"created": "Creat",
"email": "Correu electrònic",
"follow_dev": "Segueix al desenvolupador",
"github": "Projecte de GitHub",
"items": "Articles",
"join_discord": "Uniu-vos a Discord",
"locations": "Ubicacions",
"name": "Nom",
"read_docs": "Llegiu la documentació",
"search": "Cerca",
"sign_out": "Tanca la sessió",
"submit": "Envia",
"welcome": "Us donem la benvinguda, { username }",
"labels": "Etiquetes",
"password": "Contrasenya",
"version": "Versió {version}"
},
"index": {
"joining_group": "Us uniu a un grup existent!",
"dont_join_group": "Voleu unir-vos al grup?",
"login": "Inici de sessió",
"disabled_registration": "El registre és desactivat",
"register": "Registra-m'hi",
"remember_me": "Recorda'm",
"set_email": "Quin és el seu correu electrònic?",
"set_name": "Com us dieu?",
"set_password": "Definiu la contrasenya",
"tagline": "Feu el seguiment, organitzeu i gestioneu les vostres coses."
},
"items": {
"negate_labels": "Nega les etiquetes seleccionades",
"no_results": "No s'ha trobat cap element",
"tip_1": "Els filtres d'ubicació i etiquetes utilitzen l'operació «O». Si se'n selecciona més d'un, \nnomés se'n requerirà un per a coincidència.",
"add": "Afegeix",
"field_selector": "Selector del camp",
"field_value": "Valor del camp",
"first": "Primer",
"include_archive": "Inclou els articles arxivats",
"last": "Últim",
"next_page": "Pàgina següent",
"options": "Opcions",
"order_by": "Ordena per",
"tip_2": "Les cerques amb el prefix «#» sol·licitaran un ID d'un actiu (per exemple, «#000-001»)",
"tip_3": "Els filtres de camp utilitzen l'operació «O». Si se'n selecciona més d'un, \nnomés se'n requerirà un per a coincidència.",
"tips": "Consells",
"tips_sub": "Consells de cerca",
"updated_at": "Actualitzat a",
"query_id": "S'està consultant el número d'identificació de l'actiu: { id }",
"pages": "Pàgina { page } de { totalPages }",
"prev_page": "Pàgina anterior",
"reset_search": "Reinicia la cerca",
"results": "{ total } resultats",
"created_at": "Creat a",
"custom_fields": "Camps personalitzats"
},
"profile": {
"active": "Actiu",
"change_password": "Canvia contrasenya",
"delete_account_sub": "Elimina el compte i totes les dades associades. Aquesta acció no es pot desfer.",
"enabled": "Habilitat",
"currency_format": "Format de moneda",
"current_password": "Contrasenya actual",
"delete_account": "Suprimeix el compte",
"gen_invite": "Genera un enllaç d'invitació",
"group_settings": "Configuració del grup",
"group_settings_sub": "Configuració del grup compartit. És possible que hàgiu d'actualitzar la pàgina per aplicar la configuració.",
"theme_settings_sub": "La configuració del tema s'emmagatzema a l'emmagatzematge local del navegador. Podeu canviar el tema en qualsevol moment. \nSi teniu problemes per definir el tema, proveu d'actualitzar el navegador.",
"notifier_modal": "{ type, select, true {Edita} false {Crea} other {Altres}} Notificació",
"notifiers_sub": "Rebeu notificacions per als pròxims recordatoris de manteniment",
"test": "Prova",
"theme_settings": "Configuracions del tema",
"update_group": "Actualitza el grup",
"url": "URL",
"user_profile_sub": "Convida usuaris i gestiona el compte.",
"inactive": "Inactiu",
"new_password": "Contrasenya nova",
"notifiers": "Notificadors",
"user_profile": "Perfil d'usuari"
}
}

158
frontend/locales/de.json Normal file
View File

@@ -0,0 +1,158 @@
{
"components": {
"app": {
"import_dialog": {
"change_warning": "Das Verhalten beim Importieren vorhandener import_refs hat sich geändert. Wenn ein import_ref in der CSV-Datei vorhanden ist, \nwird der Gegenstand mit den Werten in der CSV-Datei aktualisiert.",
"description": "Importiere eine CSV-Datei, die deine Gegenstände, Etiketten und Standorte enthält. Schau in die Dokumentation für weitere Informationen\nzum erforderlichen Format.",
"title": "CSV-Datei importieren",
"upload": "Hochladen"
}
},
"global": {
"page_qr_code": {
"page_url": "Seiten-URL"
},
"password_score": {
"password_strength": "Passwortstärke"
}
},
"item": {
"create_modal": {
"title": "Gegenstand erstellen",
"photo_button": "Foto 📷"
},
"view": {
"selectable": {
"card": "Karte",
"items": "Gegenstände",
"no_items": "Keine Gegenstände anzuzeigen",
"table": "Tabelle"
}
}
},
"label": {
"create_modal": {
"title": "Etikett erstellen"
}
},
"location": {
"create_modal": {
"title": "Standort erstellen"
}
}
},
"global": {
"build": "Build: { build }",
"confirm": "Bestätigen",
"create": "Erstellen",
"create_and_add": "Erstellen und weiteren hinzufügen",
"created": "Erstellt",
"email": "E-Mail",
"follow_dev": "Dem Entwickler folgen",
"github": "GitHub-Projekt",
"items": "Gegenstände",
"join_discord": "Discord beitreten",
"labels": "Etiketten",
"locations": "Lagerorte",
"name": "Name",
"password": "Passwort",
"read_docs": "Dokumentation lesen",
"search": "Suche",
"sign_out": "Abmelden",
"submit": "Einreichen",
"version": "Version: { version }",
"welcome": "Willkommen, { username }"
},
"index": {
"disabled_registration": "Registrierung deaktiviert",
"dont_join_group": "Möchtest du nicht einer Gruppe beitreten?",
"joining_group": "Du trittst einer bereits bestehenden Gruppe bei!",
"login": "Anmelden",
"register": "Registrieren",
"remember_me": "Angemeldet bleiben",
"set_email": "Was ist deine E-Mail?",
"set_name": "Wie heißt du?",
"set_password": "Setze dein Passwort",
"tagline": "Verfolgen, Organisieren und Verwalten deiner Sachen."
},
"items": {
"add": "Hinzufügen",
"created_at": "Erstellt am",
"custom_fields": "Benutzerdefinierte Felder",
"field_selector": "Feldauswahl",
"field_value": "Feldwert",
"first": "Erste",
"include_archive": "Archivierte Elemente einschließen",
"last": "Letzte",
"negate_labels": "Ausgewählte Etiketten negieren",
"next_page": "Nächste Seite",
"no_results": "Keine Elemente gefunden",
"options": "Optionen",
"order_by": "Sortieren nach",
"pages": "Seite { page } von { totalPages }",
"prev_page": "Vorherige Seite",
"query_id": "Abfrage Asset-ID-Nummer: { id }",
"reset_search": "Suche zurücksetzen",
"results": "{ total } Ergebnisse",
"tip_1": "Standort- und Etikettenfilter verwenden die 'ODER'-Operation. Wenn mehr als eines ausgewählt ist, wird\n nur eines für eine Übereinstimmung benötigt.",
"tip_2": "Suchen, die mit '#' beginnen, fragen nach einer Asset-ID (Beispiel '#000-001')",
"tip_3": "Feldfilter verwenden die 'ODER'-Operation. Wenn mehr als eines ausgewählt ist, wird nur eines\n für eine Übereinstimmung benötigt.",
"tips": "Tipps",
"tips_sub": "Suchtipps",
"updated_at": "Aktualisiert am"
},
"profile": {
"active": "Aktiv",
"change_password": "Passwort ändern",
"currency_format": "Währungsformat",
"current_password": "Aktuelles Passwort",
"delete_account": "Konto löschen",
"delete_account_sub": "Lösche dein Konto und alle zugehörigen Daten. Dies kann nicht rückgängig gemacht werden.",
"enabled": "Aktiviert",
"gen_invite": "Einladungslink generieren",
"group_settings": "Gruppeneinstellungen",
"group_settings_sub": "Geteilte Gruppeneinstellungen. Möglicherweise musst du die Seite neu laden, damit einige Einstellungen wirksam werden.",
"inactive": "Inaktiv",
"new_password": "Neues Passwort",
"notifier_modal": "{ type, select, true {Bearbeiten} false {Erstellen} other {Andere}} Notifier",
"notifiers": "Melder",
"notifiers_sub": "Erhalte Benachrichtigungen über bevorstehende Wartungserinnerungen",
"test": "Test",
"theme_settings": "Themes",
"theme_settings_sub": "Theme-Einstellungen werden im lokalen Speicher deines Browsers gespeichert. Du kannst das Theme jederzeit ändern. Wenn du Probleme hast, dein Theme einzustellen, versuche, die Seite neu zu laden.",
"update_group": "Gruppe aktualisieren",
"url": "URL",
"user_profile": "Benutzerprofil",
"user_profile_sub": "Lade Benutzer ein und verwalte dein Konto.",
"language": "Sprache",
"update_language": "Sprache ändern"
},
"tools": {
"import_export_set": {
"import_button": "Inventar importieren",
"import": "Inventar importieren",
"export_sub": "Exportiert das Standard-CSV-Format für Homebox. Damit werden alle Artikel in deinem Inventar exportiert.",
"export": "Inventar exportieren",
"import_sub": "Importiert das Standard-CSV-Format für Homebox. Ohne eine '<code>'HB.import_ref'</code>' Spalte werden vorhandenen Artikel in Ihrem Bestand '<b>'nicht'</b>' überschrieben, sondern nur neue Artikel hinzugefügt. Zeilen mit einer '<code>'HB.import_ref'</code>' Spalte werden mit vorhandenen Artikeln mit der gleichen import_ref zusammengeführt, sofern vorhanden."
},
"reports_set": {
"bill_of_materials_button": "Stückliste generieren",
"asset_labels_button": "Etikettengenerator",
"bill_of_materials": "Stückliste",
"bill_of_materials_sub": "Erzeugt eine CSV-Datei (Comma Separated Values), die in ein Tabellenkalkulationsprogramm importiert werden kann. Dies ist eine Zusammenfassung des Bestands mit grundlegenden Artikel- und Preisinformationen.",
"asset_labels_sub": "Erzeugt eine druckbare PDF-Datei mit Etiketten für eine Reihe von Asset-IDs. Diese sind nicht spezifisch für deine Inventargegenstände, so dass du die Etiketten im Voraus ausdrucken und bei Erhalt auf deinen Inventargegenständen anbringen kannst.",
"asset_labels": "Asset-ID-Etiketten"
},
"import_export": "Import/Export",
"reports_sub": "Erstelle verschiedene Berichte für dein Inventar.",
"import_export_sub": "Importieren und exportieren des Inventars in und aus einer CSV-Datei. Dies ist nützlich, um das Inventar auf eine neue Instanz von Homebox zu migrieren.",
"reports": "Berichte",
"actions": "Inventar Aktionen",
"actions_sub": "Aktionen in großen Mengen auf das Inventar anwenden. Diese Aktionen sind unumkehrbar. '<b>'Sei vorsichtig.'</b>'",
"actions_set": {
"ensure_ids_button": "Sicherstellen von Asset-IDs",
"ensure_ids": "Sicherstellen von Asset-IDs",
"ensure_import_refs": "Sicherstellen, dass Import-Referenzen importiert wurden"
}
}
}

189
frontend/locales/en.json Normal file
View File

@@ -0,0 +1,189 @@
{
"components": {
"app": {
"import_dialog": {
"change_warning": "Behavior for imports with existing import_refs has changed. If an import_ref is present in the CSV file, the \nitem will be updated with the values in the CSV file.",
"description": "Import a CSV file containing your items, labels, and locations. See documentation for more information on the \nrequired format.",
"title": "Import CSV File",
"upload": "Upload"
}
},
"global": {
"page_qr_code": {
"page_url": "Page URL"
},
"password_score": {
"password_strength": "Password Strength"
}
},
"item": {
"create_modal": {
"title": "Create Item",
"photo_button": "Photo 📷"
},
"view": {
"selectable": {
"card": "Card",
"items": "Items",
"no_items": "No Items to Display",
"table": "Table"
}
}
},
"label": {
"create_modal": {
"title": "Create Label"
}
},
"location": {
"create_modal": {
"title": "Create Location"
}
}
},
"global": {
"build": "Build: { build }",
"confirm": "Confirm",
"create": "Create",
"create_and_add": "Create and Add Another",
"created": "Created",
"email": "Email",
"follow_dev": "Follow the Developer",
"github": "GitHub Project",
"items": "Items",
"join_discord": "Join the Discord",
"labels": "Labels",
"locations": "Locations",
"name": "Name",
"password": "Password",
"read_docs": "Read the Docs",
"search": "Search",
"sign_out": "Sign Out",
"submit": "Submit",
"version": "Version: { version }",
"welcome": "Welcome, { username }"
},
"index": {
"disabled_registration": "Registration Disabled",
"dont_join_group": "Don't want to join a group?",
"joining_group": "You're Joining an Existing Group!",
"login": "Login",
"register": "Register",
"remember_me": "Remember Me",
"set_email": "What's your email?",
"set_name": "What's your name?",
"set_password": "Set your password",
"tagline": "Track, Organize, and Manage your Things."
},
"items": {
"add": "Add",
"created_at": "Created At",
"custom_fields": "Custom Fields",
"field_selector": "Field Selector",
"field_value": "Field Value",
"first": "First",
"include_archive": "Include Archived Items",
"last": "Last",
"negate_labels": "Negate Selected Labels",
"next_page": "Next Page",
"no_results": "No Items Found",
"options": "Options",
"order_by": "Order By",
"pages": "Page { page } of { totalPages }",
"prev_page": "Previous Page",
"query_id": "Querying Asset ID Number: { id }",
"reset_search": "Reset Search",
"results": "{ total } Results",
"tip_1": "Location and label filters use the 'OR' operation. If more than one is selected only one will be\n required for a match.",
"tip_2": "Searches prefixed with '#'' will query for a asset ID (example '#000-001')",
"tip_3": "Field filters use the 'OR' operation. If more than one is selected only one will be required for a\n match.",
"tips": "Tips",
"tips_sub": "Search Tips",
"updated_at": "Updated At"
},
"profile": {
"active": "Active",
"change_password": "Change Password",
"currency_format": "Currency Format",
"current_password": "Current Password",
"delete_account": "Delete Account",
"delete_account_sub": "Delete your account and all its associated data. This can not be undone.",
"enabled": "Enabled",
"gen_invite": "Generate Invite Link",
"group_settings": "Group Settings",
"group_settings_sub": "Shared Group Settings. You may need to refresh your browser for some settings to apply.",
"inactive": "Inactive",
"new_password": "New Password",
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Notifier",
"notifiers": "Notifiers",
"notifiers_sub": "Get notifications for upcoming maintenance reminders",
"test": "Test",
"theme_settings": "Theme Settings",
"theme_settings_sub": "Theme settings are stored in your browser's local storage. You can change the theme at any time. If you're\n having trouble setting your theme try refreshing your browser.",
"update_group": "Update Group",
"url": "URL",
"user_profile": "User Profile",
"user_profile_sub": "Invite users, and manage your account.",
"display_header": "{ currentValue, select, true {Hide Header} false {Show Header} other {Not Hit}}",
"language": "Language",
"update_language": "Update Language"
},
"tools": {
"reports": "Reports",
"reports_sub": "Generate different reports for your inventory.",
"reports_set": {
"asset_labels": "Asset ID Labels",
"asset_labels_sub": "Generates a printable PDF of labels for a range of Asset ID. These are not specific to your inventory so you are able to print labels ahead of time and apply them to your inventory when you receive them.",
"asset_labels_button": "Label Generator",
"bill_of_materials": "Bill of Materials",
"bill_of_materials_sub": "Generates a CSV (Comma Separated Values) file that can be imported into a spreadsheet program. This is a summary of your inventory with basic item and pricing information.",
"bill_of_materials_button": "Generate BOM"
},
"import_export": "Import/Export",
"import_export_sub": "Import and export your inventory to and from a CSV file. This is useful for migrating your inventory to a new instance of Homebox.",
"import_export_set": {
"import": "Import Inventory",
"import_sub": "Imports the standard CSV format for Homebox. Without an '<code>'HB.import_ref'</code>' column, this will '<b>'not'</b>' overwrite any existing items in your inventory, only add new items. Rows with an '<code>'HB.import_ref'</code>' column are merged into existing items with the same import_ref, if one exists.",
"import_button": "Import Inventory",
"export": "Export Inventory",
"export_sub": "Exports the standard CSV format for Homebox. This will export all items in your inventory.",
"export_button": "Export Inventory"
},
"actions": "Inventory Actions",
"actions_sub": "Apply Actions to your inventory in bulk. These are irreversible actions. '<b>'Be careful.'</b>'",
"actions_set": {
"ensure_ids": "Ensure Asset IDs",
"ensure_ids_sub": "Ensures that all items in your inventory have a valid asset_id field. This is done by finding the highest current asset_id field in the database and applying the next value to each item that has an unset asset_id field. This is done in order of the created_at field.",
"ensure_ids_button": "Ensure Asset IDs",
"ensure_import_refs": "Ensure Import Refs",
"ensure_import_refs_sub": "Ensures that all items in your inventory have a valid import_ref field. This is done by randomly generating a 8 character string for each item that has an unset import_ref field.",
"ensure_import_refs_button": "Ensure Import Refs",
"zero_datetimes": "Zero Item Date Times",
"zero_datetimes_sub": "Resets the time value for all date time fields in your inventory to the beginning of the date. This is to fix a bug that was introduced early on in the development of the site that caused the time value to be stored with the time which caused issues with date fields displaying accurate values. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/issues/236\" target=\"_blank\">'See Github Issue #236 for more details.'</a>'",
"zero_datetimes_button": "Zero Item Date Times",
"set_primary_photo": "Set Primary Photo",
"set_primary_photo_sub": "In version v0.10.0 of Homebox, the primary image field was added to attachments of type photo. This action will set the primary image field to the first image in the attachments array in the database, if it is not already set. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/pull/576\">'See GitHub PR #576'</a>'",
"set_primary_photo_button": "Set Primary Photo"
}
},
"languages": {
"ca": "Catalan",
"de": "German",
"en": "English",
"es": "Spanish",
"fr": "French",
"hu": "Hungarian",
"it": "Italian",
"nl": "Dutch",
"pl": "Polish",
"pt-BR": "Portuguese (Brazil)",
"ru": "Russian",
"sl": "Slovenian",
"sv": "Swedish",
"tr": "Turkish",
"zh-CN": "Chinese (Simplified)",
"zh-HK": "Chinese (Hong Kong)",
"zh-MO": "Chinese (Macau)",
"zh-TW": "Chinese (Traditional)"
}
}

128
frontend/locales/es.json Normal file
View File

@@ -0,0 +1,128 @@
{
"components": {
"app": {
"import_dialog": {
"change_warning": "Se ha modificado el comportamiento de las importaciones con import_refs existentes. Si existe una import_ref en el archivo CSV, el \nelemento se actualizará con los valores del archivo CSV.",
"description": "Importa un archivo CSV que contenga tus elementos, etiquetas y ubicaciones. Consulta la documentación para obtener más información sobre el \nformato requerido.",
"title": "Importar Archivo CSV",
"upload": "Subir"
}
},
"global": {
"page_qr_code": {
"page_url": "URL de página"
},
"password_score": {
"password_strength": "Seguridad de la contraseña"
}
},
"item": {
"create_modal": {
"title": "Crear Elemento",
"photo_button": "Foto 📷"
},
"view": {
"selectable": {
"card": "Tarjeta",
"items": "Elementos",
"no_items": "No hay elementos para mostrar",
"table": "Tabla"
}
}
},
"label": {
"create_modal": {
"title": "Crear Etiqueta"
}
},
"location": {
"create_modal": {
"title": "Crear Ubicación"
}
}
},
"global": {
"build": "Compilación: { build }",
"confirm": "Confirmar",
"create": "Crear",
"created": "Creado",
"email": "Email",
"github": "Proyecto GitHub",
"items": "Elementos",
"join_discord": "Únete al Discord",
"labels": "Etiquetas",
"locations": "Ubicaciones",
"name": "Nombre",
"password": "Contraseña",
"read_docs": "Lee la Documentación",
"search": "Buscar",
"sign_out": "Cerrar Sesión",
"submit": "Enviar",
"welcome": "Bienvenido/a, { username }",
"create_and_add": "Crear y Añadir Otro",
"follow_dev": "Seguir al Desarrollador",
"version": "Versión: { version }"
},
"index": {
"disabled_registration": "Registro Desactivado",
"dont_join_group": "¿No quieres unirte a un grupo?",
"login": "Iniciar sesión",
"register": "Registrarse",
"remember_me": "Recuérdame",
"set_email": "¿Cuál es tu email?",
"set_name": "¿Cómo te llamas?",
"set_password": "Establece tu contraseña",
"tagline": "Rastrea, Organiza y Gestiona tus Cosas.",
"joining_group": "¡Te estás uniendo a un grupo existente!"
},
"items": {
"add": "Añadir",
"created_at": "Creado El",
"custom_fields": "Campos Personalizados",
"field_selector": "Selector de Campo",
"field_value": "Valor del Campo",
"first": "Primer",
"include_archive": "Incluir Elementos Archivados",
"last": "Último",
"negate_labels": "Negar Etiquetas Seleccionadas",
"next_page": "Siguiente Página",
"no_results": "No se Encontraron Elementos",
"options": "Opciones",
"order_by": "Ordenar Por",
"pages": "Página { page } de { totalPages }",
"prev_page": "Anterior Página",
"query_id": "Consultar Número ID del Activo: { id }",
"reset_search": "Restablecer Búsqueda",
"results": "{ total } Resultados",
"tip_2": "Las búsquedas precedidas de \"#\" buscarán un ID de activo (por ejemplo, \"#000-001\")",
"tip_3": "Los filtros de campo utilizan el operador \"OR\". Si se selecciona más de uno, sólo se requerirá uno para una\n coincidencia.",
"tip_1": "Los filtros de ubicación y etiquetas utilizan el operador \"OR\". Si se selecciona más de uno, sólo uno será\n necesario para obtener una coincidencia.",
"tips": "Sugerencias",
"tips_sub": "Sugerencias de Búsqueda",
"updated_at": "Actualizado El"
},
"profile": {
"active": "Activo",
"change_password": "Cambiar Contraseña",
"currency_format": "Formato Divisa",
"current_password": "Contraseña Actual",
"delete_account": "Eliminar Cuenta",
"enabled": "Habilitado",
"gen_invite": "Generar Enlace de Invitación",
"group_settings": "Ajustes de Grupo",
"group_settings_sub": "Configuración de Grupo Compartido. Es posible que tengas que actualizar tu navegador para que se apliquen algunos ajustes.",
"inactive": "Inactivo",
"new_password": "Nueva Contraseña",
"notifiers": "Notificaciones",
"notifiers_sub": "Recibe notificaciones de los próximos recordatorios de mantenimiento",
"test": "Probar",
"theme_settings": "Ajustes de Tema",
"update_group": "Actualizar Grupo",
"url": "URL",
"user_profile": "Perfil de Usuario",
"user_profile_sub": "Invita a usuarios y gestiona tu cuenta.",
"delete_account_sub": "Elimina tu cuenta y todos sus datos asociados. Esto no se puede deshacer.",
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Notificación",
"theme_settings_sub": "La configuración del tema se guarda en el almacenamiento local de tu navegador. Puedes cambiar el tema en cualquier momento. Si tienes\n problemas para configurar el tema, prueba a actualizar el navegador."
}
}

138
frontend/locales/fr.json Normal file
View File

@@ -0,0 +1,138 @@
{
"global": {
"email": "Courriel",
"read_docs": "Lire la documentation",
"password": "Mot de passe",
"github": "Projet GitHub",
"join_discord": "Rejoindre le Discord",
"follow_dev": "Suivre le développeur",
"version": "Version : { version }",
"build": "Assemblage : { build }",
"create": "Créer",
"submit": "Soumettre",
"create_and_add": "Créer et en ajouter un autre",
"confirm": "Confirmer",
"sign_out": "Déconnexion",
"welcome": "Bienvenue, { username }",
"created": "Créé",
"labels": "Étiquettes",
"locations": "Emplacements",
"name": "Nom",
"search": "Rechercher",
"items": "Articles"
},
"index": {
"set_email": "Quel est vôtre courriel?",
"set_name": "Quel est votre nom?",
"login": "Se connecter",
"register": "S'enregistrer",
"remember_me": "Se souvenir de moi",
"set_password": "Définir votre mot de passe",
"dont_join_group": "Vous ne voulez pas joindre un groupe ?",
"tagline": "Répertoriez, organisez et gérez vos affaires.",
"disabled_registration": "Les inscriptions sont désactivées",
"joining_group": "Vous rejoignez un groupe existant!"
},
"components": {
"label": {
"create_modal": {
"title": "Créer une étiquette"
}
},
"item": {
"view": {
"selectable": {
"items": "Articles",
"no_items": "Nb. darticles à afficher",
"table": "Tableau",
"card": "Carte"
}
},
"create_modal": {
"title": "Créer un article",
"photo_button": "Photo 📷"
}
},
"location": {
"create_modal": {
"title": "Créer un emplacement"
}
},
"global": {
"password_score": {
"password_strength": "Solidité du mot de passe"
},
"page_qr_code": {
"page_url": "URL de la page"
}
},
"app": {
"import_dialog": {
"title": "Importer un fichier CSV",
"upload": "Téléverser",
"description": "Importer un fichier CSV contenant vos articles, étiquettes, et emplacements. Voir la documentation pour plus dinformations sur le format requis.",
"change_warning": "Le comportement lors dimportations avec des import_ref existants a changé. Si une valeur pour import_ref est présente dans le fichier CSV, alors lélément sera mis à jour avec celle-ci."
}
}
},
"profile": {
"notifiers_sub": "Recevez des notifications pour vous prévenir des prochaines maintenances.",
"active": "Actif",
"enabled": "Activé",
"test": "Test",
"current_password": "Mot de passe actuel",
"new_password": "Nouveau mot de passe",
"change_password": "Changer de mot de passe",
"inactive": "Inactif",
"notifiers": "Notifications",
"gen_invite": "Générer un lien dinvitation",
"user_profile": "Profil utilisateur",
"user_profile_sub": "Gérez votre compte et invitez des utilisateurs.",
"url": "URL",
"delete_account": "Effacer le compte",
"delete_account_sub": "Supprimer le compte et toutes ses données. Aucune récupération possible.",
"group_settings": "Paramètres du groupe",
"group_settings_sub": "Paramètres du groupe partagé. Il peut être nécessaire de recharger la page pour quils deviennent effectifs.",
"currency_format": "Format de la devise",
"update_group": "Mettre à jour le groupe",
"theme_settings": "Paramètres du thème",
"theme_settings_sub": "Les paramètres du thème sont stockés dans le navigateur. Vous pouvez les changer à tout moment. Si vous\nrencontrez des problèmes, il est conseillé de rafraichir la page.",
"notifier_modal": "Notifications { type, select, true {Edit} false {Create} other {Other}}",
"update_language": "Mettre à jour la langue",
"language": "Langue"
},
"items": {
"add": "Ajouter",
"created_at": "Créé à",
"custom_fields": "Champs personnalisés",
"field_selector": "Sélecteur de champ",
"field_value": "Valeur du champ",
"first": "Premier",
"include_archive": "Inclure les éléments archivés",
"last": "Dernier",
"negate_labels": "Négliger les étiquettes sélectionnées",
"next_page": "Page suivante",
"no_results": "Aucun élément trouvé",
"options": "Options",
"order_by": "Trier par",
"pages": "Page { page } sur { totalPages }",
"prev_page": "Page précédente",
"query_id": "Interrogation du numéro d'identification de l'actif : { id }",
"reset_search": "Réinitialiser la recherche",
"tip_1": "Les filtres demplacement et détiquette utilisent lopérateur « OU ».\nUn seul des filtres na besoin de correspondre pour retourner un résultat.",
"tip_2": "Les recherches préfixées par '#'' rechercheront un ID d'actif (exemple '#000-001')",
"tip_3": "Les filtres de champ utilisent lopérateur « OU ».\nUn seul des filtres na besoin de correspondre pour retourner un résultat.",
"tips": "Conseils",
"tips_sub": "Conseils pour la recherche",
"updated_at": "Mis à jour le",
"results": "{ total } résultats"
},
"tools": {
"reports_set": {
"bill_of_materials": "Nomenclature",
"asset_labels_button": "Générateur détiquettes"
},
"reports": "Rapports",
"reports_sub": "Générez différents rapports pour votre inventaire."
}
}

84
frontend/locales/hu.json Normal file
View File

@@ -0,0 +1,84 @@
{
"components": {
"app": {
"import_dialog": {
"upload": "Feltöltés",
"title": "CSV fájl importálása"
}
},
"global": {
"page_qr_code": {
"page_url": "Oldal URL"
},
"password_score": {
"password_strength": "Jelszó erőssége"
}
},
"item": {
"create_modal": {
"title": "Tétel létrehozása",
"photo_button": "Fénykép 📷"
},
"view": {
"selectable": {
"card": "Kártya",
"items": "Tételek",
"no_items": "Nincs megjeleníthető tétel",
"table": "Táblázat"
}
}
},
"label": {
"create_modal": {
"title": "Címke létrehozása"
}
},
"location": {
"create_modal": {
"title": "Helyszín létrehozása"
}
}
},
"global": {
"build": "Build: { build }",
"confirm": "Megerősítés",
"create": "Létrehozás",
"create_and_add": "Létrehozás és újabb hozzáadása",
"created": "Létrehozva",
"email": "Email",
"follow_dev": "Fejlesztő követése",
"github": "GitHub Projekt",
"items": "Tételek",
"join_discord": "Csatlakozás a Discordhoz",
"labels": "Címkék",
"locations": "Helyszínek",
"name": "Név",
"password": "Jelszó",
"read_docs": "Dokumentáció elolvasása",
"search": "Keresés",
"sign_out": "Kilépés",
"submit": "Elküldés",
"version": "Verzió: { version }",
"welcome": "Üdv, { username }"
},
"index": {
"disabled_registration": "Regisztráció kikapcsolva",
"dont_join_group": "Nem akarsz csoporthoz csatlakozni?",
"joining_group": "Egy létező csoporthoz csatlakozol!",
"login": "Belépés",
"register": "Regisztráció",
"remember_me": "Emlékezzen rám",
"set_email": "Mi az email címed?",
"set_name": "Mi a neved?",
"set_password": "Jelszó beállítása",
"tagline": "Kövesd, rendszerezd és igazgasd a tárgyaidat."
},
"items": {
"add": "Hozzáadás",
"created_at": "Létrehozás dátuma",
"field_selector": "Mezőválasztó",
"field_value": "Mező értéke",
"first": "Első",
"custom_fields": "Egyedi mezők"
}
}

127
frontend/locales/it.json Normal file
View File

@@ -0,0 +1,127 @@
{
"components": {
"app": {
"import_dialog": {
"change_warning": "Il comportamento per le importazioni con import_ref esistenti è cambiato. Se un import_ref è presente nel file CSV,\n l'elemento verrà aggiornato con i valori presenti nel file CSV.",
"description": "Importa un file CSV contenente gli oggetti, le etichette e le posizioni. Vedi la documentazione per ulteriori informazioni sul \nformato richiesto.",
"title": "Importa File CSV",
"upload": "Carica"
}
},
"global": {
"page_qr_code": {
"page_url": "URL della Pagina"
},
"password_score": {
"password_strength": "Complessità della Password"
}
},
"item": {
"create_modal": {
"title": "Crea Oggetto"
},
"view": {
"selectable": {
"card": "Scheda",
"items": "Oggetti",
"no_items": "Nessun Oggetto da Visualizzare",
"table": "Tabella"
}
}
},
"label": {
"create_modal": {
"title": "Crea Etichetta"
}
},
"location": {
"create_modal": {
"title": "Crea Posizione"
}
}
},
"global": {
"build": "Build: { build }",
"confirm": "Conferma",
"create": "Crea",
"create_and_add": "Crea e aggiungi un altro",
"created": "Creato",
"email": "Email",
"follow_dev": "Segui lo Sviluppatore",
"github": "Progetto GitHub",
"items": "Articoli",
"join_discord": "Unisciti a Discord",
"labels": "Etichette",
"locations": "Posizioni",
"name": "Nome",
"password": "Password",
"read_docs": "Leggi la Documentazione",
"search": "Cerca",
"sign_out": "Disconnetti",
"submit": "Invia",
"version": "Versione: { version }",
"welcome": "Benvenuto, { username }"
},
"index": {
"disabled_registration": "Registrazione Disabilitata",
"dont_join_group": "Non vuoi unirti a un gruppo?",
"joining_group": "Stai unendoti a un gruppo esistente!",
"login": "Accedi",
"register": "Registrati",
"remember_me": "Ricordami",
"set_email": "Qual è la tua email?",
"set_name": "Come ti chiami?",
"set_password": "Imposta la tua password",
"tagline": "Tieni traccia, Organizza e Gestisci le tue Cose."
},
"items": {
"add": "Aggiungi",
"created_at": "Creato Il",
"custom_fields": "Campi Personalizzati",
"field_selector": "Selettore di Campo",
"field_value": "Valore del Campo",
"first": "Primo",
"include_archive": "Includi Articoli Archiviati",
"last": "Ultimo",
"negate_labels": "Negare Etichette Selezionate",
"next_page": "Pagina Successiva",
"no_results": "Nessun Articolo Trovato",
"options": "Opzioni",
"order_by": "Ordina Per",
"pages": "Pagina { page } di { totalPages }",
"prev_page": "Pagina Precedente",
"query_id": "ID dell'Asset in Ricerca: { id }",
"reset_search": "Reimposta Ricerca",
"results": "{ total } Risultati",
"tip_1": "I filtri di posizione ed etichetta utilizzano l'operazione 'OR'. Se ne viene selezionato più\n di uno, ne sarà richiesto solo uno per una corrispondenza.",
"tip_2": "Le ricerche con prefisso '#' cercheranno un ID asset (esempio '#000-001')",
"tip_3": "I filtri di campo utilizzano l'operazione 'OR'. Se ne viene selezionato più di uno, ne sarà\n richiesto solo uno per una corrispondenza.",
"tips": "Suggerimenti",
"tips_sub": "Suggerimenti per la Ricerca",
"updated_at": "Aggiornato Il"
},
"profile": {
"active": "Attivo",
"change_password": "Cambia Password",
"currency_format": "Formato Valuta",
"current_password": "Password Corrente",
"delete_account": "Elimina Account",
"delete_account_sub": "Elimina il tuo account e tutti i dati associati. Questa operazione non può essere annullata.",
"enabled": "Abilitato",
"gen_invite": "Genera Link di Invito",
"group_settings": "Impostazioni Gruppo",
"group_settings_sub": "Impostazioni Gruppo Condivise. Potrebbe essere necessario aggiornare il browser affinché alcune impostazioni vengano applicate.",
"inactive": "Inattivo",
"new_password": "Nuova Password",
"notifier_modal": "{ type, select, true {Modifica} false {Crea} other {Altro}} Notifier",
"notifiers": "Notifiche",
"notifiers_sub": "Ricevi notifiche per i prossimi promemoria di manutenzione",
"test": "Test",
"theme_settings": "Impostazioni Tema",
"theme_settings_sub": "Le impostazioni del tema sono memorizzate nella memoria locale del tuo browser. Puoi cambiare il tema \nin qualsiasi momento. Se hai problemi a impostare il tuo tema, prova a ricaricare la pagina.",
"update_group": "Aggiorna Gruppo",
"url": "URL",
"user_profile": "Profilo Utente",
"user_profile_sub": "Invita utenti e gestisci il tuo account."
}
}

128
frontend/locales/nl.json Normal file
View File

@@ -0,0 +1,128 @@
{
"global": {
"version": "Versie: { version }",
"github": "GitHub Project",
"join_discord": "Sluit je aan bij de Discord",
"follow_dev": "Volg de ontwikkelaar",
"read_docs": "Lees de documentatie",
"email": "E-mail",
"submit": "Indienen",
"confirm": "Bevestigen",
"create": "Maken",
"create_and_add": "Maak en voeg nog een toe",
"password": "Wachtwoord",
"build": "Bouw: { build }",
"created": "Gemaakt",
"welcome": "Welkom, { username }",
"sign_out": "Log uit",
"labels": "etiketten",
"locations": "Locaties",
"name": "Naam",
"items": "artikelen",
"search": "Zoeken"
},
"index": {
"disabled_registration": "Registratie uitgeschakeld",
"login": "Log in",
"register": "Registreer",
"remember_me": "Onthoud mij",
"set_email": "Wat is je mailadres?",
"set_password": "Stel je wachtwoord in",
"set_name": "Wat is je naam?",
"joining_group": "Je neemt deel aan een bestaande groep!",
"tagline": "Volg, Organiseer en beheer je dingen.",
"dont_join_group": "Wil je niet aan een groep deelnemen?"
},
"components": {
"global": {
"password_score": {
"password_strength": "Wachtwoord sterkte"
},
"page_qr_code": {
"page_url": "Pagina URL"
}
},
"app": {
"import_dialog": {
"title": "Importeer CSV bestand",
"change_warning": "Gedrag voor importeren met bestaande import_refs is veranderd. Als een import_refs reeds bestaat in het CSV bestand, het\nobject zal worden aangepast met de waardes van het CSV bestand.",
"upload": "Upload",
"description": "Importeer een CSV bestand met je objecten, labels en locaties. Zie documentatie voor meer informatie m.b.t.\n vereist format."
}
},
"item": {
"create_modal": {
"title": "Maak object",
"photo_button": "Foto 📷"
},
"view": {
"selectable": {
"items": "Objecten",
"card": "Kaart",
"table": "Tabel",
"no_items": "Geen objecten om te tonen"
}
}
},
"label": {
"create_modal": {
"title": "Maak label"
}
},
"location": {
"create_modal": {
"title": "Maak locatie"
}
}
},
"profile": {
"gen_invite": "Genereer Uitnodigingslink",
"notifier_modal": "{ type, select, true {Bewerk} false {Creëer} other {overig}} Notifier",
"change_password": "Verander Wachtwoord",
"current_password": "Huidig Wachtwoord",
"new_password": "Nieuw Wachtwoord",
"url": "URL",
"enabled": "ingeschakeld",
"test": "Test",
"user_profile": "Gebruikers Profiel",
"user_profile_sub": "Nodig gebruikers uit, en beheer je account.",
"active": "Actief",
"inactive": "Inactief",
"notifiers_sub": "Krijg notificaties voor opkomende onderhouds herinneringen",
"group_settings_sub": "Gedeelde groepsinstellingen",
"group_settings": "Groeps Instellingen",
"notifiers": "Notificatie",
"currency_format": "Valutanotatie",
"update_group": "Groep bijwerken",
"theme_settings": "Theme instellingen",
"theme_settings_sub": "Thema-instellingen worden opgeslagen in de lokale opslag van uw browser. Je kan deze wijzigen op elk moment. \nAls je problemen hebt met de instellingen kun je je browser verversen.",
"delete_account": "Verwijder account",
"delete_account_sub": "Verwijder je account en alle geassocieerde data. Deze actie kan niet ongedaan worden."
},
"items": {
"tip_1": "Locatie- en labelfilters gebruiken de 'OF' -werking. Als er meer dan een is geselecteerd,\nis er maar een nodig voor een overeenkomst.",
"tip_2": "Zoekopdrachten voorafgegaan door '#'' zullen om een object-ID vragen (bijvoorbeeld '#000-001')",
"tip_3": "Veldfilters gebruiken de 'OF' -bewerking. Indien meer dan 1 is geselecteerd\nzal er maar 1 nodig zijn voor een match.",
"tips_sub": "Zoektips",
"updated_at": "Bijgewerkt op",
"pages": "Pagina { page } van { totalPages }",
"prev_page": "Vorige pagina",
"query_id": "ID-nummer van object opvragen: { id }",
"reset_search": "Reset Zoeken",
"tips": "Tips",
"no_results": "Geen Items Gevonden",
"options": "Opties",
"order_by": "Sorteren op",
"results": "{ total } Resultaten",
"add": "Toevoegen",
"created_at": "Aangemaakt op",
"custom_fields": "Aangepaste velden",
"field_selector": "Veld selectie",
"field_value": "Veldwaarde",
"first": "Eerst",
"include_archive": "Inclusief gearchiveerde items",
"last": "Achternaam",
"negate_labels": "Negeer Geselecteerde Etiketten",
"next_page": "Volgende pagina"
}
}

128
frontend/locales/pl.json Normal file
View File

@@ -0,0 +1,128 @@
{
"components": {
"app": {
"import_dialog": {
"description": "Zaimportuj plik CSV zawierający Twoje przedmioty, etykiety i lokalizacje. Zobacz dokumentację, aby uzyskać \nwięcej informacji na temat wymaganego formatu.",
"title": "Zaimportuj plik CSV",
"upload": "Prześlij",
"change_warning": "Zachowanie przy imporcie z istniejącymi import_ref zostało zmienione. Jeśli import_ref jest obecny w pliku CSV, \nprzedmiot zostanie zaktualizowany zgodnie z wartościami w pliku CSV."
}
},
"global": {
"page_qr_code": {
"page_url": "Adres URL strony"
},
"password_score": {
"password_strength": "Siła hasła"
}
},
"item": {
"create_modal": {
"title": "Utwórz przedmiot",
"photo_button": "Zdjęcie 📷"
},
"view": {
"selectable": {
"card": "Karta",
"items": "Przedmioty",
"no_items": "Brak przedmiotów do wyświetlenia",
"table": "Tabela"
}
}
},
"label": {
"create_modal": {
"title": "Stwórz nową etykietę"
}
},
"location": {
"create_modal": {
"title": "Utwórz lokalizację"
}
}
},
"global": {
"build": "Kompilacja: {build}",
"follow_dev": "Śledź dewelopera",
"github": "Projekt na GitHubie",
"items": "Przedmioty",
"version": "Wersja:{version}",
"welcome": "Witaj, {username}",
"confirm": "Potwierdź",
"create": "Utwórz",
"create_and_add": "Utwórz i dodaj kolejny",
"created": "Utworzone",
"email": "E-mail",
"join_discord": "Dołącz do Discorda",
"labels": "Etykiety",
"locations": "Lokalizacje",
"name": "Nazwa",
"password": "Hasło",
"read_docs": "Przeczytaj dokumentację",
"search": "Wyszukaj",
"sign_out": "Wyloguj się",
"submit": "Wyślij"
},
"index": {
"set_password": "Ustaw swoje hasło",
"dont_join_group": "Nie chcesz dołączyć do grupy?",
"disabled_registration": "Rejestracja jest wyłączona",
"joining_group": "Dołączasz do istniejącej grupy!",
"login": "Zaloguj się",
"register": "Zarejestruj się",
"remember_me": "Zapamiętaj mnie",
"set_email": "Jaki jest Twój adres e-mail?",
"set_name": "Jak się nazywasz?",
"tagline": "Śledź, organizuj i zarządzaj swoimi rzeczami."
},
"items": {
"created_at": "Data utworzenia",
"field_selector": "Selektor pól",
"field_value": "Wartość pola",
"first": "Pierwszy",
"include_archive": "Uwzględnij zarchiwizowane przedmioty",
"negate_labels": "Neguj wybrane etykiety",
"no_results": "Nie znaleziono przedmiotów",
"query_id": "Zapytanie o numer identyfikacyjny zasobu: { id }",
"results": "{ total } wyniki",
"tip_3": "Filtry pól używają operacji 'LUB'. Jeśli wybrano więcej niż jeden, wystarczy jeden, \naby uzyskać dopasowanie.",
"updated_at": "Zaktualizowano",
"tip_1": "Filtry lokalizacji i etykiet używają operacji 'LUB'. Jeśli wybrano więcej niż jeden, wystarczy jeden, \naby uzyskać dopasowanie.",
"pages": "Strona {page} z {totalPages}",
"add": "Dodaj",
"custom_fields": "Pola niestandardowe",
"last": "Ostatni",
"next_page": "Następna strona",
"options": "Opcje",
"prev_page": "Poprzednia strona",
"reset_search": "Zresetuj wyszukiwanie",
"tip_2": "Wyszukiwania poprzedzone prefiksem \"#\" będą wysyłać zapytanie o identyfikator zasobu (na przykład \"#000-001\")",
"tips": "Wskazówki",
"tips_sub": "Wskazówki wyszukiwania",
"order_by": "Ułóż według"
},
"profile": {
"active": "Aktywny",
"change_password": "Zmiana hasła",
"currency_format": "Format waluty",
"delete_account": "Usuń konto",
"delete_account_sub": "Usuń swoje konto oraz wszystkie powiązane z nim dane. Tego nie można cofnąć.",
"current_password": "Bieżące hasło",
"group_settings_sub": "Ustawienia grupy udostępnione. Możesz potrzebować odświeżyć przeglądarkę, aby niektóre ustawienia zostały zastosowane.",
"inactive": "Nieaktywny",
"new_password": "Nowe hasło",
"notifier_modal": "{type, select, true {Edytuj} false {Utwórz} other {Inny}} Powiadomiacz",
"enabled": "Włączone",
"gen_invite": "Wygeneruj link z zaproszeniem",
"group_settings": "Ustawienia grupy",
"notifiers": "Powiadomiacze",
"notifiers_sub": "Otrzymuj powiadomienia o nadchodzących przypomnieniach o konserwacji",
"theme_settings_sub": "Ustawienia motywu są przechowywane w lokalnej pamięci przeglądarki. Możesz zmienić motyw w dowolnym momencie. \nJeśli masz problemy z ustawieniem motywu, spróbuj odświeżyć przeglądarkę.",
"test": "Test",
"theme_settings": "Ustawienia tematu",
"update_group": "Zaktualizuj grupę",
"url": "Adres URL",
"user_profile": "Profil użytkownika",
"user_profile_sub": "Zaproś użytkowników i zarządzaj swoim kontem."
}
}

View File

@@ -0,0 +1 @@
{}

128
frontend/locales/ru.json Normal file
View File

@@ -0,0 +1,128 @@
{
"components": {
"app": {
"import_dialog": {
"title": "Импорт CSV файла",
"change_warning": "Изменено поведение для импортов с существующими import_ref. Если в CSV-файле присутствует import_ref, \nто элемент будет обновлен значениями из CSV-файла.",
"description": "Импортируйте CSV-файл, содержащий ваши предметы, ярлыки и местоположения. Для получения информации о требуемом формате \nсм. документацию.",
"upload": "Загрузить"
}
},
"global": {
"page_qr_code": {
"page_url": "URL-адрес страницы"
},
"password_score": {
"password_strength": "Сложность пароля"
}
},
"item": {
"create_modal": {
"title": "Создать элемент",
"photo_button": "Фото 📷"
},
"view": {
"selectable": {
"items": "Элементы",
"no_items": "Нет элементов для отображения",
"table": "Таблица",
"card": "Карта"
}
}
},
"location": {
"create_modal": {
"title": "Создать локацию"
}
},
"label": {
"create_modal": {
"title": "Создать ярлык"
}
}
},
"global": {
"email": "Email",
"items": "Элементы",
"build": "Сборка: { build }",
"confirm": "Подтвердить",
"create": "Создать",
"create_and_add": "Создать и добавить еще",
"created": "Создано",
"follow_dev": "Следить за разработчиком",
"github": "Проект Github",
"labels": "Ярлыки",
"join_discord": "Присоединяйтесь к Discord",
"locations": "Локации",
"name": "Имя",
"password": "Пароль",
"read_docs": "Прочитать документацию",
"search": "Поиск",
"sign_out": "Выйти",
"submit": "Отправить",
"version": "Версия: { version }",
"welcome": "Добро пожаловать, { username }"
},
"index": {
"login": "Войти",
"register": "Зарегистрироваться",
"set_email": "Какой у вас адрес электронной почты?",
"set_name": "Как вас зовут?",
"set_password": "Установите пароль",
"tagline": "Отслеживайте, упорядочивайте и управляйте своими вещами.",
"disabled_registration": "Регистрация отключена",
"dont_join_group": "Не хотите ли вступить в группу?",
"remember_me": "Запомнить меня",
"joining_group": "Вы присоединяетесь к уже существующей группе!"
},
"items": {
"add": "Добавить",
"created_at": "Создано в",
"custom_fields": "Настраиваемые поля",
"first": "Первый",
"pages": "Страница {page} из {totalPages}",
"prev_page": "Предыдущая страница",
"reset_search": "Сбросить Поиск",
"field_selector": "Поле выбора",
"field_value": "Значение поля",
"last": "Последний",
"negate_labels": "Снять выбранные ярлыки",
"next_page": "Следующая страница",
"no_results": "Элементы не найдены",
"options": "Параметры",
"results": "{ total } Результатов",
"tips": "Подсказки",
"tips_sub": "Поисковые подсказки",
"updated_at": "Обновлено в",
"tip_3": "Фильтры по полю используют операцию «ИЛИ». Если выбрано несколько фильтров, для совпадения\n требуется только один.",
"tip_2": "Поисковые запросы с префиксом \"#\" должны включать в себя ID актива (прим. '#000-001')",
"include_archive": "Включить архивированные элементы",
"order_by": "Сортировка по",
"query_id": "Запрос идентификационного номера актива: { id }",
"tip_1": "При фильтрации по локации и по ярлыкам используется логический оператор «ИЛИ». Если выбрано несколько фильтров, то для срабатывания\n требуется лишь одно совпадение."
},
"profile": {
"new_password": "Новый пароль",
"update_group": "Обновить группу",
"active": "Активный",
"change_password": "Изменить пароль",
"currency_format": "Формат валюты",
"current_password": "Текущий пароль",
"delete_account": "Удалить аккаунт",
"group_settings": "Настройки группы",
"inactive": "Неактивный",
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Уведомитель",
"notifiers": "Уведомители",
"notifiers_sub": "Получить уведомление о предстоящем обслуживании",
"test": "Тест",
"theme_settings": "Настройки темы",
"url": "URL",
"user_profile": "Профиль пользователя",
"user_profile_sub": "Приглашайте пользователей и управляйте своим аккаунтом.",
"enabled": "Активен",
"theme_settings_sub": "Настройки темы хранятся в локальном хранилище браузера. Вы можете изменить тему в любое время. Если у вас\n не удается установить тему, попробуйте перезапустить браузер.",
"delete_account_sub": "Удалить свой аккаунт и все связанные с ним данные. Это действие невозможно отменить.",
"gen_invite": "Сгенерировать ссылку-приглашение",
"group_settings_sub": "Настройки общей группы. Для применения изменений возможно потребуется перезагрузить страницу."
}
}

128
frontend/locales/sl.json Normal file
View File

@@ -0,0 +1,128 @@
{
"global": {
"follow_dev": "Sledi razvijjalcu",
"build": "Gradnja: [ build ]",
"confirm": "Potrdi",
"create": "Ustvari",
"create_and_add": "Ustvari in dodaj še enega",
"created": "Ustvarjeno",
"email": "E-pošta",
"github": "GitHub projekt",
"items": "Predmeti",
"join_discord": "Pridruži se na Discord",
"labels": "Oznake",
"locations": "Lokacije",
"name": "Naziv",
"password": "Geslo",
"read_docs": "Preberite dokumentacijo",
"search": "Iskanje",
"sign_out": "Odjava",
"submit": "Pošlji",
"version": "Verzija: { version }",
"welcome": "Dobrodošel, { username }"
},
"components": {
"app": {
"import_dialog": {
"change_warning": "Vedenje pri uvozih z obstoječimi import_refs se je spremenilo. Če je import_ref prisoten v datoteki CSV, bo\nelement posodobljen z vrednostmi v datoteki CSV.",
"description": "Uvozite datoteko CSV, ki vsebuje vaše predmete, oznake in lokacije. Poglejte si dokumentacijo za več informacij o\nzahtevani obliki.",
"title": "Uvozi CSV datoteko",
"upload": "Naloži"
}
},
"global": {
"page_qr_code": {
"page_url": "URL strani"
},
"password_score": {
"password_strength": "Moč gesla"
}
},
"item": {
"create_modal": {
"title": "Ustvari predmet",
"photo_button": "Fotografija 📷"
},
"view": {
"selectable": {
"card": "Kartica",
"items": "Predmeti",
"no_items": "Ni predmetov za prikaz",
"table": "Tabela"
}
}
},
"label": {
"create_modal": {
"title": "Ustvari oznako"
}
},
"location": {
"create_modal": {
"title": "Ustvari lokacijo"
}
}
},
"index": {
"disabled_registration": "Registracija je onemogočena",
"dont_join_group": "Se ne želite pridružiti skupini?",
"joining_group": "Pridružujete se obstoječi skupini!",
"login": "Prijava",
"register": "Registriraj se",
"remember_me": "Zapomni si me",
"set_email": "Kakšen je vaš e-poštni naslov?",
"set_password": "Nastavite geslo",
"tagline": "Sledite, organizirajte in upravljajte svoje stvari.",
"set_name": "Kako vam je ime?"
},
"items": {
"created_at": "Ustvarjeno ob",
"include_archive": "Vključi arhivirane predmete",
"last": "Zadnji",
"negate_labels": "Negiraj izbrane oznake",
"no_results": "Ni najdenih predmetov",
"options": "Možnosti",
"pages": "Stran { page } od { totalPages }",
"tip_2": "Iskanja s predpono '#' bodo iskala ID sredstva (primer '#000-001')",
"tip_3": "Filtri polj uporabljajo operacijo 'ALI'. Če je izbranih več kot eno, bo za ujemanje dovolj samo\n eno.",
"tips": "Nasveti",
"updated_at": "Posodobljeno ob",
"custom_fields": "Polje po meri",
"results": "Rezultatov: { total }",
"tip_1": "Filtri lokacij in oznak uporabljajo operacijo 'ALI'. Če je izbranih več kot ena, bo izbrana samo ena\n potrebna za ujemanje.",
"add": "Dodaj",
"field_selector": "Izbirnik polj",
"field_value": "Vrednost polja",
"first": "Prvi",
"next_page": "Naslednja stran",
"prev_page": "Prejšnja stran",
"query_id": "Poizvedovanje po identifikacijski številki sredstva: { id }",
"reset_search": "Ponastavi iskanje",
"tips_sub": "Nasveti za iskanje",
"order_by": "Razvrsti po"
},
"profile": {
"gen_invite": "Ustvari povezavo povabila",
"group_settings_sub": "Nastavitve skupine v skupni rabi. Morda boste morali osvežiti brskalnik, da bodo nekatere nastavitve veljale.",
"notifiers": "Obveščevalci",
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Obveščevalec",
"notifiers_sub": "Prejemajte obvestila za prihajajoče opomnike o vzdrževanju",
"theme_settings_sub": "Nastavitve teme so shranjene v lokalni shrambi vašega brskalnika. Temo lahko kadar koli spremenite. Če\n imate težave z nastavitvijo teme, poskusite osvežiti brskalnik.",
"update_group": "Posodobi skupino",
"url": "URL",
"delete_account_sub": "Izbrišite svoj račun in vse z njim povezane podatke. Tega ni mogoče razveljaviti.",
"active": "Aktivno",
"change_password": "Sprememba gesla",
"currency_format": "Oblika valute",
"current_password": "Trenutno geslo",
"delete_account": "Izbriši račun",
"enabled": "Omogočeno",
"group_settings": "Nastavitve skupine",
"inactive": "Neaktivno",
"new_password": "Novo geslo",
"test": "Preizkus",
"theme_settings": "Nastavitve teme",
"user_profile": "Profil uporabnika",
"user_profile_sub": "Povabite uporabnike in upravljajte svoj račun."
}
}

128
frontend/locales/sv.json Normal file
View File

@@ -0,0 +1,128 @@
{
"profile": {
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Anmälare",
"change_password": "Ändra Lösenord",
"current_password": "Nuvarande lösenord",
"new_password": "Nytt lösenord",
"notifiers_sub": "Få aviseringar om kommande underhållspåminnelser",
"url": "URL",
"test": "Test",
"gen_invite": "Skapa inbjudningslänk",
"user_profile": "Användarprofil",
"user_profile_sub": "Bjud in användare och hantera ditt konto.",
"active": "Aktiv",
"inactive": "Inaktiv",
"notifiers": "Notiser",
"enabled": "Aktiverad",
"currency_format": "Valuta format",
"delete_account": "Radera konto",
"delete_account_sub": "Ta bort ditt konto och alla tillhörande data. Detta kan inte ångras.",
"group_settings": "Grupp inställningar",
"group_settings_sub": "Inställningar för delad grupp. Du kan behöva uppdatera din webbläsare för att vissa inställningar ska gälla.",
"theme_settings": "Temainställningar",
"theme_settings_sub": "Temainställningar sparas i din webbläsares lokala lagring. Du kan ändra tema när du vill. Om du\nhar problem att ställa in tema, pröva att ladda om din webbläsare.",
"update_group": "Uppdatera grupp"
},
"index": {
"set_name": "Vad heter du?",
"joining_group": "Du går med i en befintlig grupp!",
"dont_join_group": "Vill du inte gå med i en grupp?",
"tagline": "Spåra, organisera och hantera dina saker.",
"disabled_registration": "Registrering avaktiverad",
"login": "Logga in",
"register": "Registrera",
"remember_me": "Kom ihåg mig",
"set_email": "Vad är din e-post?",
"set_password": "Ställ in ditt lösenord"
},
"components": {
"app": {
"import_dialog": {
"upload": "Ladda upp",
"title": "Importera CSV fil",
"description": "Importera en CSV-fil som innehåller dina föremål, etiketter och platser. Se dokumentationen för mer information om \nönskat format.",
"change_warning": "Beteendet för importer med befintliga import_refs har ändrats. Om en import_ref finns i CSV-filen, \nobjektet kommer att uppdateras med värdena i CSV-filen."
}
},
"item": {
"view": {
"selectable": {
"card": "Kort",
"items": "Föremål",
"table": "Tabell",
"no_items": "Inga föremål att visa"
}
},
"create_modal": {
"title": "Skapa föremål",
"photo_button": "Foto 📷"
}
},
"label": {
"create_modal": {
"title": "Skapa etikett"
}
},
"location": {
"create_modal": {
"title": "Skapa plats"
}
},
"global": {
"password_score": {
"password_strength": "Lösenordsstyrka"
},
"page_qr_code": {
"page_url": "Sidans URL"
}
}
},
"global": {
"build": "Byggd: { build }",
"github": "GitHub Projekt",
"join_discord": "Gå med i Discord",
"follow_dev": "Följ utvecklaren",
"read_docs": "Läs dokumenten",
"password": "Lösenord",
"email": "Epost",
"submit": "Skicka",
"confirm": "Godkänn",
"create": "Skapa",
"created": "Skapad",
"welcome": "Välkommen, { username }",
"sign_out": "Logga ut",
"create_and_add": "Skapa och lägg till en annan",
"version": "Version: { version }",
"items": "Föremål",
"labels": "Etiketter",
"locations": "Platser",
"name": "Namn",
"search": "Sök"
},
"items": {
"add": "Lägg till",
"created_at": "Skapat",
"custom_fields": "Egna fält",
"field_selector": "Fält alternativ",
"field_value": "Fält värde",
"first": "Första",
"include_archive": "Inkludera arkiverade föremål",
"last": "Sista",
"negate_labels": "Negera valda etiketter",
"next_page": "Nästa sida",
"no_results": "Inga föremål hittades",
"options": "Alternativ",
"order_by": "Ordning via",
"pages": "Sida { page } av { totalPages }",
"prev_page": "Föregående sida",
"query_id": "Fråga efter tillgångs-ID-nummer: { id }",
"reset_search": "Återställ sökning",
"tip_1": "Platser och etiketter filter använder 'OR' funktionen. Om fler än en är valda, endast en kommer\nkrävas för en träff.",
"tip_2": "Sökningar med prefixet '#'' kommer att fråga efter ett tillgångs-ID (exempel '#000-001')",
"tip_3": "Fältfilter använder 'OR' funktion. Om fler än en är valda endast en kommer att bli krävande för en\nträff.",
"tips": "Tips",
"tips_sub": "Sök Tips",
"updated_at": "Uppdaterad",
"results": "{ total } Resultat"
}
}

128
frontend/locales/tr.json Normal file
View File

@@ -0,0 +1,128 @@
{
"global": {
"version": "Versiyon:{ version }",
"password": "Şifre",
"create": "Oluştur",
"github": "GitHub projesi",
"join_discord": "Discord'a Katılın",
"follow_dev": "Geliştiriciyi takip edin",
"read_docs": "Dokümanları okuyun",
"email": "Elektronik posta",
"submit": "Gönder",
"confirm": "Onaylayın",
"build": "Sürüm: { build }",
"create_and_add": "Oluştur ve Bir Tane Daha Ekle",
"created": "Oluşturuldu",
"items": "Öğeler",
"labels": "Etiketler",
"locations": "Konumlar",
"name": "İsim",
"search": "Ara",
"sign_out": "Oturumu kapat",
"welcome": "Hoşgeldib, { username }"
},
"components": {
"app": {
"import_dialog": {
"change_warning": "Mevcut import_refs ile içe aktarmaların davranışı değişti. CSV dosyasında bir import_ref varsa, \nöğe CSV dosyasındaki değerlerle güncellenecektir.",
"title": "CSV dosyasını içeri aktar",
"upload": "Yükle",
"description": "Öğelerinizi, etiketlerinizi ve konumlarınızı içeren bir CSV dosyasını içe aktarın. Daha fazla\nbilgi için dökümanları okuyun."
}
},
"global": {
"password_score": {
"password_strength": "Şifre güvenlik seviyesi"
},
"page_qr_code": {
"page_url": "Sayfa URL'si"
}
},
"item": {
"create_modal": {
"title": "Eşya Oluştur",
"photo_button": "Fotoğraf 📷"
},
"view": {
"selectable": {
"items": "Öğeler",
"card": "Kart",
"table": "Tablo",
"no_items": "Görüntülecek Öge Yok"
}
}
},
"label": {
"create_modal": {
"title": "Etiket oluştur"
}
},
"location": {
"create_modal": {
"title": "Konum oluştur"
}
}
},
"index": {
"remember_me": "Beni Hatırla",
"tagline": "Eşyalarınızı Takip Edin, Düzenleyin ve Yönetin.",
"disabled_registration": "Kayıt olma devre dışı",
"login": "Oturum Aç",
"register": "Kaydolun",
"set_email": "E-posta adresiniz nedir?",
"set_password": "Şifrenizi belirleyin",
"set_name": "Adın ne?",
"joining_group": "Mevcut bir gruba katılıyorsunuz!",
"dont_join_group": "Bir gruba katılmak istemiyor musunuz?"
},
"items": {
"add": "Ekle",
"created_at": "Oluşturulma",
"custom_fields": "Özel Alanlar",
"field_selector": "alan seçici",
"field_value": "Alan Değeri",
"first": "Birinci",
"include_archive": "Arşivlenen Öğeleri Dahil Et",
"last": "Son",
"negate_labels": "Seçili Etiketleri Yoksay",
"next_page": "Sonraki Sayfa",
"no_results": "Öğe Bulunamadı",
"options": "Seçenekler",
"order_by": "Sıralama ölçütü",
"pages": "Sayfa { page }/{ totalPages }",
"prev_page": "Önceki Sayfa",
"query_id": "Varlık Kimlik Numarası Sorgulanıyor: { id }",
"tip_1": "Konum ve etiket filtreleri 'veya' işlemini kullanır. Eğer birden fazla seçilirse sadece biri \neşleştirme için kullanılacaktır.",
"reset_search": "Aramayı Sıfırla",
"tips": "İpuçları",
"results": "{ total } Sonuç",
"tip_2": "'#' ile başlayan aramalar bir varlık kimliğini sorgular (örneğin '#000-001')",
"tip_3": "Alan filtreleri 'VEYA' işlemini kullanır. Birden fazla seçenek seçilirse, eşleşme için yalnızca birinin \nkarşılanması yeterlidir.",
"tips_sub": "Arama İpuçları",
"updated_at": "Güncellendiği Zaman"
},
"profile": {
"url": "URL",
"user_profile": "Kullanıcı Profili",
"user_profile_sub": "Kullanıcıları davet edin ve hesabınızı yönetin.",
"active": "Aktif",
"change_password": "Şifre Değiştir",
"currency_format": "Para Birimi Biçimi",
"current_password": "Mevcut Şifre",
"delete_account": "Hesabı Sil",
"delete_account_sub": "Hesabınızı ve ona bağlı tüm verileri silin. Bu işlem geri alınamaz.",
"enabled": "Etkinleştirildi",
"gen_invite": "Davet Bağlantısı Oluştur",
"group_settings": "Grup Ayarları",
"group_settings_sub": "Paylaşılan Grup Ayarları. Bazı ayarların uygulanabilmesi için tarayıcınızı yenilemeniz gerekebilir.",
"inactive": "Etkin Değil",
"new_password": "Yeni Şifre",
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Bildirici",
"notifiers": "Bildirimde Bulunanlar",
"test": "Test",
"theme_settings": "Tema Ayarları",
"theme_settings_sub": "Tema ayarları tarayıcınızın yerel depolama alanında saklanır. Temayı istediğiniz zaman değiştirebilirsiniz. \nTemanızı ayarlamakta sorun yaşıyorsanız, tarayıcınızı yenilemeyi deneyin.",
"update_group": "Grubu Güncelle",
"notifiers_sub": "Yaklaşan bakım hatırlatmaları için bildirimler alın"
}
}

130
frontend/locales/zh-CN.json Normal file
View File

@@ -0,0 +1,130 @@
{
"components": {
"app": {
"import_dialog": {
"title": "导入 CSV 文件",
"upload": "上传",
"description": "导入包含物品、标签和位置的CSV文件。更多有关信息\n请参阅文档查看所需格式。",
"change_warning": "导入行为会导致现有的 import_refs 的字段被覆盖。\n如果 CSV 文件中存在 import_ref则将使用 CSV 文件中的值更新该项。"
}
},
"item": {
"create_modal": {
"title": "创建物品",
"photo_button": "照片 📷"
},
"view": {
"selectable": {
"card": "卡片模式",
"items": "物品",
"no_items": "没有可展示的物品",
"table": "表格模式"
}
}
},
"location": {
"create_modal": {
"title": "创建位置"
}
},
"global": {
"page_qr_code": {
"page_url": "页面URL"
},
"password_score": {
"password_strength": "密码强度"
}
},
"label": {
"create_modal": {
"title": "创建标签"
}
}
},
"global": {
"create_and_add": "保存并继续创建",
"email": "邮箱",
"items": "物品",
"build": "编译:{build}",
"confirm": "确认",
"create": "创建",
"created": "已创建",
"follow_dev": "关注开发者",
"github": "Github项目",
"join_discord": "加入Discord讨论",
"labels": "标签",
"sign_out": "注销",
"version": "版本:{version}",
"welcome": "欢迎,{username}",
"name": "名称",
"password": "密码",
"read_docs": "查阅文档",
"search": "搜索",
"submit": "提交",
"locations": "位置"
},
"index": {
"disabled_registration": "已禁用注册",
"dont_join_group": "不想加入群组?",
"joining_group": "您正在加入现有群组!",
"login": "登录",
"register": "注册",
"set_name": "你的名称叫什么?",
"remember_me": "记住我",
"set_email": "您的电子邮箱是什么?",
"tagline": "跟踪、整理和管理您的物品。",
"set_password": "设置密码"
},
"items": {
"last": "最后一页",
"negate_labels": "取消选中的标签",
"next_page": "下一页",
"no_results": "没有可显示的物品",
"options": "选项",
"prev_page": "上一页",
"add": "添加",
"field_selector": "字段选择",
"first": "第一页",
"order_by": "排序方式",
"pages": "第{page}页,共{totalPages}页",
"query_id": "查询资产ID {id}",
"created_at": "创建于",
"custom_fields": "自定义字段",
"field_value": "字段值",
"include_archive": "包括已存档的项目",
"reset_search": "重置搜索",
"results": "{ total } 条结果",
"tip_1": "位置和标签过滤器使用“或”操作。如果选择了多个位置或标签,\n则只需要任意一个匹配上即可。",
"tip_2": "以“#”为前缀的搜索将变成查询资产ID例如“#000-001” ",
"tip_3": "字段过滤器使用“或”操作。如果选择了多个位置或标签,\n则只需要任意一个匹配上即可。",
"tips": "建议",
"tips_sub": "搜索提示",
"updated_at": "更新于"
},
"profile": {
"group_settings_sub": "共享组设置。您可能需要刷新浏览器来让某些设置生效。",
"notifier_modal": "{ type, select, true {编辑} false {创建} other {Other}} 通知器",
"active": "活跃",
"delete_account_sub": "删除您的帐户及其所有相关数据。这是无法撤消的。",
"inactive": "非活跃",
"theme_settings_sub": "主题设置存储在浏览器的本地存储中。您可以随时更改主题。\n如果您在设置主题时遇到问题请尝试刷新浏览器。",
"update_group": "更新组",
"change_password": "更改密码",
"currency_format": "货币格式",
"current_password": "原密码",
"delete_account": "删除帐户",
"enabled": "已启用",
"gen_invite": "生成邀请链接",
"group_settings": "组设置",
"new_password": "新密码",
"notifiers": "通知器",
"notifiers_sub": "获取即将到来的维护提醒通知",
"test": "测试",
"theme_settings": "主题设置",
"url": "网址",
"user_profile": "用户资料",
"user_profile_sub": "邀请用户共同管理您的资产。",
"update_language": "更新语言",
"language": "语言"
}
}

128
frontend/locales/zh-HK.json Normal file
View File

@@ -0,0 +1,128 @@
{
"items": {
"include_archive": "包括存檔項目",
"tip_1": "位置和標籤過濾器使用“OR”運算。如果選擇了多個則一次配對\n只會使用一個。",
"pages": "第{ page } 頁(共{ totalPages })",
"tip_3": "字段過濾器使用“OR”運算。如果選擇了多個則一次配對只需要選擇\n一個。",
"field_value": "欄位值",
"field_selector": "欄位選擇器",
"custom_fields": "自訂欄位",
"first": "第一項",
"add": "添加",
"created_at": "創建於",
"last": "最後一項",
"negate_labels": "取消選定的標籤",
"next_page": "下一頁",
"no_results": "沒有找到項目",
"options": "選項",
"order_by": "排序",
"prev_page": "上一頁",
"query_id": "查詢資產ID號碼:{ id }",
"reset_search": "重置搜尋",
"results": "{ total }結果",
"tips": "提示",
"tips_sub": "搜尋技巧",
"updated_at": "更新於",
"tip_2": "以「#」為前綴的搜尋將查詢資產 ID例如「#000-001」"
},
"components": {
"app": {
"import_dialog": {
"description": "匯入包含您的商品、標籤和位置的 CSV 檔案。有關所需格式的更多信息\n請參閱文件。",
"title": "匯入CSV檔案",
"upload": "上傳",
"change_warning": "現有 import_refs 的導入行為已變更。如果 CSV 檔案中存在 import_ref則\n項目將使用 CSV 檔案中的值進行更新。"
}
},
"global": {
"page_qr_code": {
"page_url": "頁面網址"
},
"password_score": {
"password_strength": "密碼強度"
}
},
"item": {
"create_modal": {
"title": "新增項目",
"photo_button": "相片 📷"
},
"view": {
"selectable": {
"card": "卡牌風",
"items": "項目",
"no_items": "沒有可顯示的項目",
"table": "表格"
}
}
},
"label": {
"create_modal": {
"title": "新增標籤"
}
},
"location": {
"create_modal": {
"title": "新增地點"
}
}
},
"global": {
"build": "建立{ build }",
"confirm": "確認",
"create": "新增",
"create_and_add": "創建並添加另一個",
"created": "已建立",
"email": "電子信箱",
"github": "GitHub項目",
"items": "項目",
"join_discord": "加入Discord",
"labels": "標籤",
"locations": "地點",
"name": "名字",
"password": "密碼",
"read_docs": "閱讀文件",
"search": "搜尋",
"sign_out": "登出",
"submit": "提交",
"version": "版本:{ version }",
"follow_dev": "關注開發者",
"welcome": "歡迎,{ username }"
},
"index": {
"disabled_registration": "已禁用註冊",
"dont_join_group": "不想加入群組?",
"joining_group": "您正在加入現有群組!",
"login": "登入",
"register": "註冊",
"remember_me": "記住帳號",
"set_email": "你的電子郵件是什麼?",
"set_name": "你叫什麼名字?",
"tagline": "追蹤、組織和管理您的物品。",
"set_password": "設定您的密碼"
},
"profile": {
"change_password": "變更密碼",
"currency_format": "貨幣格式",
"current_password": "當前密碼",
"delete_account": "刪除帳戶",
"enabled": "啟用",
"gen_invite": "產生邀請連結",
"group_settings": "群組設定",
"group_settings_sub": "共享群組設定。您可能需要刷新瀏覽器才能套用設定。",
"inactive": "不活躍",
"new_password": "新密碼",
"active": "活躍",
"delete_account_sub": "刪除您的帳戶及其所有相關資料。此操作無法撤回。",
"notifiers": "通知者",
"notifiers_sub": "獲取即將到來的維護提醒的通知",
"test": "測試",
"theme_settings": "主題設定",
"update_group": "更新群組",
"url": "網址",
"user_profile": "使用者資料",
"user_profile_sub": "邀請使用者並管理您的帳戶。",
"theme_settings_sub": "主題設定儲存在瀏覽器的本機儲存中。您可以隨時變更主題。如果你是\n 設定主題時遇到問題,請嘗試刷新瀏覽器。",
"notifier_modal": "通知 { type, select, true {Edit} false {Create} other {Other}}"
}
}

128
frontend/locales/zh-MO.json Normal file
View File

@@ -0,0 +1,128 @@
{
"items": {
"include_archive": "包括存檔項目",
"tip_1": "位置和標籤過濾器使用“OR”運算。如果選擇了多個則一次配對\n只會使用一個。",
"pages": "第{ page } 頁(共{ totalPages })",
"tip_3": "字段過濾器使用“OR”運算。如果選擇了多個則一次配對只需要選擇\n一個。",
"field_value": "欄位值",
"field_selector": "欄位選擇器",
"custom_fields": "自訂欄位",
"first": "第一項",
"add": "添加",
"created_at": "創建於",
"last": "最後一項",
"negate_labels": "取消選定的標籤",
"next_page": "下一頁",
"no_results": "沒有找到項目",
"options": "選項",
"order_by": "排序",
"prev_page": "上一頁",
"query_id": "查詢資產ID號碼:{ id }",
"reset_search": "重置搜尋",
"results": "{ total }結果",
"tips": "提示",
"tips_sub": "搜尋技巧",
"updated_at": "更新於",
"tip_2": "以「#」為前綴的搜尋將查詢資產 ID例如「#000-001」"
},
"components": {
"app": {
"import_dialog": {
"description": "匯入包含您的商品、標籤和位置的 CSV 檔案。有關所需格式的更多信息\n請參閱文件。",
"title": "匯入CSV檔案",
"upload": "上傳",
"change_warning": "現有 import_refs 的導入行為已變更。如果 CSV 檔案中存在 import_ref則\n項目將使用 CSV 檔案中的值進行更新。"
}
},
"global": {
"page_qr_code": {
"page_url": "頁面網址"
},
"password_score": {
"password_strength": "密碼強度"
}
},
"item": {
"create_modal": {
"title": "新增項目",
"photo_button": "相片 📷"
},
"view": {
"selectable": {
"card": "卡牌風",
"items": "項目",
"no_items": "沒有可顯示的項目",
"table": "表格"
}
}
},
"label": {
"create_modal": {
"title": "新增標籤"
}
},
"location": {
"create_modal": {
"title": "新增地點"
}
}
},
"global": {
"build": "建立{ build }",
"confirm": "確認",
"create": "新增",
"create_and_add": "創建並添加另一個",
"created": "已建立",
"email": "電子信箱",
"github": "GitHub項目",
"items": "項目",
"join_discord": "加入Discord",
"labels": "標籤",
"locations": "地點",
"name": "名字",
"password": "密碼",
"read_docs": "閱讀文件",
"search": "搜尋",
"sign_out": "登出",
"submit": "提交",
"version": "版本:{ version }",
"follow_dev": "關注開發者",
"welcome": "歡迎,{ username }"
},
"index": {
"disabled_registration": "已禁用註冊",
"dont_join_group": "不想加入群組?",
"joining_group": "您正在加入現有群組!",
"login": "登入",
"register": "註冊",
"remember_me": "記住帳號",
"set_email": "你的電子郵件是什麼?",
"set_name": "你叫什麼名字?",
"tagline": "追蹤、組織和管理您的物品。",
"set_password": "設定您的密碼"
},
"profile": {
"change_password": "變更密碼",
"currency_format": "貨幣格式",
"current_password": "當前密碼",
"delete_account": "刪除帳戶",
"enabled": "啟用",
"gen_invite": "產生邀請連結",
"group_settings": "群組設定",
"group_settings_sub": "共享群組設定。您可能需要刷新瀏覽器才能套用設定。",
"inactive": "不活躍",
"new_password": "新密碼",
"active": "活躍",
"delete_account_sub": "刪除您的帳戶及其所有相關資料。此操作無法撤回。",
"notifiers": "通知者",
"notifiers_sub": "獲取即將到來的維護提醒的通知",
"test": "測試",
"theme_settings": "主題設定",
"update_group": "更新群組",
"url": "網址",
"user_profile": "使用者資料",
"user_profile_sub": "邀請使用者並管理您的帳戶。",
"theme_settings_sub": "主題設定儲存在瀏覽器的本機儲存中。您可以隨時變更主題。如果你是\n 設定主題時遇到問題,請嘗試刷新瀏覽器。",
"notifier_modal": "通知 { type, select, true {Edit} false {Create} other {Other}}"
}
}

128
frontend/locales/zh-TW.json Normal file
View File

@@ -0,0 +1,128 @@
{
"items": {
"include_archive": "包括存檔項目",
"tip_1": "位置和標籤過濾器使用“OR”運算。如果選擇了多個則一次配對\n只會使用一個。",
"pages": "第{ page } 頁(共{ totalPages })",
"tip_3": "字段過濾器使用“OR”運算。如果選擇了多個則一次配對只需要選擇\n一個。",
"field_value": "欄位值",
"field_selector": "欄位選擇器",
"custom_fields": "自訂欄位",
"first": "第一項",
"add": "添加",
"created_at": "創建於",
"last": "最後一項",
"negate_labels": "取消選定的標籤",
"next_page": "下一頁",
"no_results": "沒有找到項目",
"options": "選項",
"order_by": "排序",
"prev_page": "上一頁",
"query_id": "查詢資產ID號碼:{ id }",
"reset_search": "重置搜尋",
"results": "{ total }結果",
"tips": "提示",
"tips_sub": "搜尋技巧",
"updated_at": "更新於",
"tip_2": "以「#」為前綴的搜尋將查詢資產 ID例如「#000-001」"
},
"components": {
"app": {
"import_dialog": {
"description": "匯入包含您的商品、標籤和位置的 CSV 檔案。有關所需格式的更多信息\n請參閱文件。",
"title": "匯入CSV檔案",
"upload": "上傳",
"change_warning": "現有 import_refs 的導入行為已變更。如果 CSV 檔案中存在 import_ref則\n項目將使用 CSV 檔案中的值進行更新。"
}
},
"global": {
"page_qr_code": {
"page_url": "頁面網址"
},
"password_score": {
"password_strength": "密碼強度"
}
},
"item": {
"create_modal": {
"title": "新增項目",
"photo_button": "相片 📷"
},
"view": {
"selectable": {
"card": "卡牌風",
"items": "項目",
"no_items": "沒有可顯示的項目",
"table": "表格"
}
}
},
"label": {
"create_modal": {
"title": "新增標籤"
}
},
"location": {
"create_modal": {
"title": "新增地點"
}
}
},
"global": {
"build": "建立{ build }",
"confirm": "確認",
"create": "新增",
"create_and_add": "創建並添加另一個",
"created": "已建立",
"email": "電子信箱",
"github": "GitHub項目",
"items": "項目",
"join_discord": "加入Discord",
"labels": "標籤",
"locations": "地點",
"name": "名字",
"password": "密碼",
"read_docs": "閱讀文件",
"search": "搜尋",
"sign_out": "登出",
"submit": "提交",
"version": "版本:{ version }",
"follow_dev": "關注開發者",
"welcome": "歡迎,{ username }"
},
"index": {
"disabled_registration": "已禁用註冊",
"dont_join_group": "不想加入群組?",
"joining_group": "您正在加入現有群組!",
"login": "登入",
"register": "註冊",
"remember_me": "記住帳號",
"set_email": "你的電子郵件是什麼?",
"set_name": "你叫什麼名字?",
"tagline": "追蹤、組織和管理您的物品。",
"set_password": "設定您的密碼"
},
"profile": {
"change_password": "變更密碼",
"currency_format": "貨幣格式",
"current_password": "當前密碼",
"delete_account": "刪除帳戶",
"enabled": "啟用",
"gen_invite": "產生邀請連結",
"group_settings": "群組設定",
"group_settings_sub": "共享群組設定。您可能需要刷新瀏覽器才能套用設定。",
"inactive": "不活躍",
"new_password": "新密碼",
"active": "活躍",
"delete_account_sub": "刪除您的帳戶及其所有相關資料。此操作無法撤回。",
"notifiers": "通知者",
"notifiers_sub": "獲取即將到來的維護提醒的通知",
"test": "測試",
"theme_settings": "主題設定",
"update_group": "更新群組",
"url": "網址",
"user_profile": "使用者資料",
"user_profile_sub": "邀請使用者並管理您的帳戶。",
"theme_settings_sub": "主題設定儲存在瀏覽器的本機儲存中。您可以隨時變更主題。如果你是\n 設定主題時遇到問題,請嘗試刷新瀏覽器。",
"notifier_modal": "通知 { type, select, true {Edit} false {Create} other {Other}}"
}
}

View File

@@ -3,6 +3,9 @@ import { defineNuxtConfig } from "nuxt/config";
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
ssr: false,
build: {
transpile: ["vue-i18n"],
},
modules: [
"@nuxtjs/tailwindcss",
"@pinia/nuxt",

View File

@@ -14,50 +14,54 @@
"test:watch": " TEST_SHUTDOWN_API_SERVER=false vitest --config ./test/vitest.config.ts"
},
"devDependencies": {
"@faker-js/faker": "^8.0.0",
"@iconify-json/mdi": "^1.1.64",
"@nuxtjs/eslint-config-typescript": "^12.0.0",
"@types/dompurify": "^3.0.0",
"@types/markdown-it": "^13.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@faker-js/faker": "^8.4.1",
"@iconify-json/mdi": "^1.2.0",
"@intlify/unplugin-vue-i18n": "^4.0.0",
"@nuxtjs/eslint-config-typescript": "^12.1.0",
"@types/dompurify": "^3.0.5",
"@types/markdown-it": "^13.0.9",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vite-pwa/nuxt": "^0.5.0",
"eslint": "^8.23.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.4.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-tailwindcss": "^3.17.4",
"eslint-plugin-vue": "^9.28.0",
"h3": "^1.7.1",
"intl-messageformat": "^10.5.14",
"isomorphic-fetch": "^3.0.0",
"nuxt": "3.6.5",
"prettier": "^3.2.5",
"typescript": "^5.0.0",
"prettier": "^3.3.3",
"typescript": "^5.5.4",
"unplugin-icons": "^0.18.5",
"vite-plugin-eslint": "^1.8.1",
"vitest": "^1.0.0"
"vitest": "^1.6.0",
"vue-i18n": "^9.14.0"
},
"dependencies": {
"@headlessui/vue": "^1.7.9",
"@nuxtjs/tailwindcss": "^6.1.3",
"@pinia/nuxt": "^0.5.0",
"@tailwindcss/aspect-ratio": "^0.4.0",
"@tailwindcss/forms": "^0.5.2",
"@tailwindcss/typography": "^0.5.4",
"@headlessui/vue": "^1.7.22",
"@nuxtjs/tailwindcss": "^6.12.1",
"@pinia/nuxt": "^0.5.4",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@types/lunr": "^2.3.7",
"@vuepic/vue-datepicker": "^8.1.1",
"@vueuse/nuxt": "^10.0.0",
"@vueuse/router": "^10.0.0",
"autoprefixer": "^10.4.8",
"daisyui": "^2.24.0",
"date-fns": "^3.3.1",
"dompurify": "^3.0.0",
"h3": "^1.7.1",
"@vuepic/vue-datepicker": "^8.8.1",
"@vueuse/nuxt": "^10.11.1",
"@vueuse/router": "^10.11.1",
"autoprefixer": "^10.4.20",
"daisyui": "^2.52.0",
"date-fns": "^3.6.0",
"dompurify": "^3.1.6",
"h3": "^1.12.0",
"http-proxy": "^1.18.1",
"lunr": "^2.3.9",
"markdown-it": "^14.0.0",
"pinia": "^2.0.21",
"postcss": "^8.4.16",
"tailwindcss": "^3.1.8",
"vue": "v3.4.8",
"vue-router": "4"
"markdown-it": "^14.1.0",
"pinia": "^2.2.2",
"postcss": "^8.4.45",
"tailwindcss": "^3.4.10",
"vue": "3.4.8",
"vue-router": "^4.4.3"
}
}

View File

@@ -8,8 +8,8 @@
</script>
<template>
<h1 class="text-blue-500 font-extrabold flex flex-col text-center">
<h1 class="flex flex-col text-center font-extrabold text-blue-500">
<span class="text-7xl">404.</span>
<span class="text-5xl mt-5"> Page Not Found </span>
<span class="mt-5 text-5xl"> Page Not Found </span>
</h1>
</template>

View File

@@ -34,7 +34,7 @@
<BaseContainer>
<section v-if="!pending">
<BaseSectionHeader class="mb-5"> This Asset Id is associated with multiple items</BaseSectionHeader>
<div class="grid gap-2 grid-cols-1 sm:grid-cols-2">
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<ItemCard v-for="item in items" :key="item.id" :item="item" />
</div>
</section>

View File

@@ -38,23 +38,23 @@
<Subtitle> Recently Added </Subtitle>
<BaseCard v-if="breakpoints.lg">
<ItemViewTable :items="itemTable.items" />
<ItemViewTable :items="itemTable.items" disable-controls />
</BaseCard>
<div v-else class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div v-else class="grid grid-cols-1 gap-4 md:grid-cols-2">
<ItemCard v-for="item in itemTable.items" :key="item.id" :item="item" />
</div>
</section>
<section>
<Subtitle> Storage Locations </Subtitle>
<div class="grid grid-cols-1 sm:grid-cols-2 card md:grid-cols-3 gap-4">
<div class="card grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
<LocationCard v-for="location in locations" :key="location.id" :location="location" />
</div>
</section>
<section>
<Subtitle> Labels </Subtitle>
<div class="flex gap-4 flex-wrap">
<div class="flex flex-wrap gap-4">
<LabelChip v-for="label in labels" :key="label.id" size="lg" :label="label" class="shadow-md" />
</div>
</section>

View File

@@ -1,6 +1,5 @@
<script setup lang="ts">
import MdiGithub from "~icons/mdi/github";
import MdiTwitter from "~icons/mdi/twitter";
import MdiDiscord from "~icons/mdi/discord";
import MdiFolder from "~icons/mdi/folder";
import MdiAccount from "~icons/mdi/account";
@@ -8,6 +7,7 @@
import MdiLogin from "~icons/mdi/login";
import MdiArrowRight from "~icons/mdi/arrow-right";
import MdiLock from "~icons/mdi/lock";
import MdiMastodon from "~icons/mdi/mastodon";
useHead({
title: "Homebox | Organize and Tag Your Stuff",
@@ -77,6 +77,15 @@
async function registerUser() {
loading.value = true;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email.value)) {
toast.error("Invalid email address");
loading.value = false;
return;
}
const { error } = await api.register({
name: username.value,
email: email.value,
@@ -126,9 +135,9 @@
</script>
<template>
<div class="flex flex-col min-h-screen">
<div class="fill-primary min-w-full absolute top-0 z-[-1]">
<div class="bg-primary flex-col flex min-h-[20vh]" />
<div class="flex min-h-screen flex-col">
<div class="absolute top-0 z-[-1] min-w-full fill-primary">
<div class="flex min-h-[20vh] flex-col bg-primary" />
<svg
class="fill-primary drop-shadow-xl"
xmlns="http://www.w3.org/2000/svg"
@@ -142,49 +151,64 @@
</svg>
</div>
<div>
<header class="p-4 sm:px-6 lg:p-14 sm:py-6 sm:flex sm:items-end mx-auto">
<header class="mx-auto p-4 sm:flex sm:items-end sm:p-6 lg:p-14">
<div>
<h2 class="mt-1 text-4xl font-bold tracking-tight text-neutral-content sm:text-5xl lg:text-6xl flex">
<h2 class="mt-1 flex text-4xl font-bold tracking-tight text-neutral-content sm:text-5xl lg:text-6xl">
HomeB
<AppLogo class="w-12 -mb-4" />
<AppLogo class="-mb-4 w-12" />
x
</h2>
<p class="ml-1 text-lg text-base-content/50">Track, Organize, and Manage your Things.</p>
<p class="ml-1 text-lg text-base-content/50">{{ $t("index.tagline") }}</p>
</div>
<div class="flex mt-6 sm:mt-0 gap-4 ml-auto text-neutral-content">
<a class="tooltip" data-tip="Project Github" href="https://github.com/sysadminsmedia/homebox" target="_blank">
<MdiGithub class="h-8 w-8" />
<div class="ml-auto mt-6 flex gap-4 text-neutral-content sm:mt-0">
<a
class="tooltip"
:data-tip="$t('global.github')"
href="https://github.com/sysadminsmedia/homebox"
target="_blank"
>
<MdiGithub class="size-8" />
</a>
<a href="https://twitter.com/haybytes" class="tooltip" data-tip="Follow The Developer" target="_blank">
<MdiTwitter class="h-8 w-8" />
<a
href="https://noc.social/@sysadminszone"
class="tooltip"
:data-tip="$t('global.follow_dev')"
target="_blank"
>
<MdiMastodon class="size-8" />
</a>
<a href="https://discord.gg/tuncmNrE4z" class="tooltip" data-tip="Join The Discord" target="_blank">
<MdiDiscord class="h-8 w-8" />
<a href="https://discord.gg/aY4DCkpNA9" class="tooltip" :data-tip="$t('global.join_discord')" target="_blank">
<MdiDiscord class="size-8" />
</a>
<a href="https://hay-kot.github.io/homebox/" class="tooltip" data-tip="Read The Docs" target="_blank">
<MdiFolder class="h-8 w-8" />
<a
href="https://homebox.software/en/"
class="tooltip"
:data-tip="$t('global.read_docs')"
target="_blank"
>
<MdiFolder class="size-8" />
</a>
</div>
</header>
<div class="grid p-6 sm:place-items-center min-h-[50vh]">
<div class="grid min-h-[50vh] p-6 sm:place-items-center">
<div>
<Transition name="slide-fade">
<form v-if="registerForm" @submit.prevent="registerUser">
<div class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl">
<div class="card bg-base-100 shadow-xl md:w-[500px]">
<div class="card-body">
<h2 class="card-title text-2xl align-center">
<MdiAccount class="mr-1 w-7 h-7" />
Register
<h2 class="card-title text-2xl">
<MdiAccount class="mr-1 size-7" />
{{ $t("index.register") }}
</h2>
<FormTextField v-model="email" label="Set your email?" />
<FormTextField v-model="username" label="What's your name?" />
<div v-if="!(groupToken == '')" class="pt-4 pb-1 text-center">
<p>You're Joining an Existing Group!</p>
<FormTextField v-model="email" :label="$t('index.set_email')" />
<FormTextField v-model="username" :label="$t('index.set_name')" />
<div v-if="!(groupToken == '')" class="pb-1 pt-4 text-center">
<p>{{ $t("index.joining_group") }}</p>
<button type="button" class="text-xs underline" @click="groupToken = ''">
Don't Want To Join a Group?
{{ $t("index.dont_join_group") }}
</button>
</div>
<FormPassword v-model="password" label="Set your password" />
<FormPassword v-model="password" :label="$t('index.set_password')" />
<PasswordScore v-model:valid="canRegister" :password="password" />
<div class="card-actions justify-end">
<button
@@ -193,28 +217,32 @@
:class="loading ? 'loading' : ''"
:disabled="loading || !canRegister"
>
Register
{{ $t("index.register") }}
</button>
</div>
</div>
</div>
</form>
<form v-else @submit.prevent="login">
<div class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl">
<div class="card bg-base-100 shadow-xl md:w-[500px]">
<div class="card-body">
<h2 class="card-title text-2xl align-center">
<MdiAccount class="mr-1 w-7 h-7" />
Login
<h2 class="card-title text-2xl">
<MdiAccount class="mr-1 size-7" />
{{ $t("index.login") }}
</h2>
<template v-if="status && status.demo">
<p class="text-xs italic text-center">This is a demo instance</p>
<p class="text-xs text-center"><b>Email</b> demo@example.com</p>
<p class="text-xs text-center"><b>Password</b> demo</p>
<p class="text-center text-xs italic">This is a demo instance</p>
<p class="text-center text-xs">
<b>{{ $t("global.email") }}</b> demo@example.com
</p>
<p class="text-center text-xs">
<b>{{ $t("global.password") }}</b> demo
</p>
</template>
<FormTextField v-model="email" label="Email" />
<FormPassword v-model="loginPassword" label="Password" />
<FormTextField v-model="email" :label="$t('global.email')" />
<FormPassword v-model="loginPassword" :label="$t('global.password')" />
<div class="max-w-[140px]">
<FormCheckbox v-model="remember" label="Remember Me" />
<FormCheckbox v-model="remember" :label="$t('index.remember_me')" />
</div>
<div class="card-actions justify-end">
<button
@@ -223,36 +251,39 @@
:class="loading ? 'loading' : ''"
:disabled="loading"
>
Login
{{ $t("index.login") }}
</button>
</div>
</div>
</div>
</form>
</Transition>
<div class="text-center mt-6">
<div class="mt-6 text-center">
<BaseButton
v-if="status && status.allowRegistration"
class="btn-primary btn-wide"
@click="() => toggleLogin()"
>
<template #icon>
<MdiAccountPlus v-if="!registerForm" class="w-5 h-5 swap-off" />
<MdiLogin v-else class="w-5 h-5 swap-off" />
<MdiArrowRight class="w-5 h-5 swap-on" />
<MdiAccountPlus v-if="!registerForm" class="swap-off size-5" />
<MdiLogin v-else class="swap-off size-5" />
<MdiArrowRight class="swap-on size-5" />
</template>
{{ registerForm ? "Login" : "Register" }}
{{ registerForm ? $t("index.login") : $t("index.register") }}
</BaseButton>
<p v-else class="text-base-content italic text-sm inline-flex items-center gap-2">
<MdiLock class="w-4 h-4 inline-block" />
Registration Disabled
<p v-else class="inline-flex items-center gap-2 text-sm italic text-base-content">
<MdiLock class="inline-block size-4" />
{{ $t("index.disabled_registration") }}
</p>
</div>
</div>
</div>
</div>
<footer v-if="status" class="mt-auto text-center w-full bottom-0 pb-4">
<p class="text-center text-sm">Version: {{ status.build.version }} ~ Build: {{ status.build.commit }}</p>
<footer v-if="status" class="bottom-0 mt-auto w-full pb-4 text-center">
<p class="text-center text-sm">
{{ $t("global.version", { version: status.build.version }) }} ~
{{ $t("global.build", { build: status.build.commit }) }}
</p>
</footer>
</div>
</template>

View File

@@ -176,6 +176,10 @@
name: "Insured",
text: item.value?.insured ? "Yes" : "No",
},
{
name: "Archived",
text: item.value?.archived ? "Yes" : "No",
},
{
name: "Notes",
type: "markdown",
@@ -441,42 +445,42 @@
<template>
<BaseContainer v-if="item" class="pb-8">
<Title>{{ item.name }}</Title>
<dialog ref="refDialog" class="z-[999] fixed bg-transparent">
<dialog ref="refDialog" class="fixed z-[999] bg-transparent">
<div ref="refDialogBody" class="relative">
<div class="absolute right-0 -mt-3 -mr-3 sm:-mt-4 sm:-mr-4 space-x-1">
<a class="btn btn-sm sm:btn-md btn-primary btn-circle" :href="dialoged.src" download>
<MdiDownload class="h-5 w-5" />
<div class="absolute right-0 -mr-3 -mt-3 space-x-1 sm:-mr-4 sm:-mt-4">
<a class="btn btn-circle btn-primary btn-sm sm:btn-md" :href="dialoged.src" download>
<MdiDownload class="size-5" />
</a>
<button class="btn btn-sm sm:btn-md btn-primary btn-circle" @click="closeDialog()">
<MdiClose class="h-5 w-5" />
<button class="btn btn-circle btn-primary btn-sm sm:btn-md" @click="closeDialog()">
<MdiClose class="size-5" />
</button>
</div>
<img class="max-w-[80vw] max-h-[80vh]" :src="dialoged.src" />
<img class="max-h-[80vh] max-w-[80vw]" :src="dialoged.src" />
</div>
</dialog>
<section>
<div class="bg-base-100 rounded p-3">
<div class="rounded bg-base-100 p-3">
<header class="mb-2">
<div class="flex flex-wrap items-end gap-2">
<div class="avatar placeholder mb-auto">
<div class="bg-neutral-focus text-neutral-content rounded-full w-12">
<MdiPackageVariant class="h-7 w-7" />
<div class="w-12 rounded-full bg-neutral-focus text-neutral-content">
<MdiPackageVariant class="size-7" />
</div>
</div>
<div>
<div v-if="fullpath && fullpath.length > 0" class="text-sm breadcrumbs pt-0 pb-0">
<div v-if="fullpath && fullpath.length > 0" class="breadcrumbs py-0 text-sm">
<ul class="text-base-content/70">
<li v-for="part in fullpath" :key="part.id">
<NuxtLink :to="`/${part.type}/${part.id}`"> {{ part.name }}</NuxtLink>
</li>
</ul>
</div>
<h1 class="text-2xl pb-1">
<h1 class="pb-1 text-2xl">
{{ item ? item.name : "" }}
</h1>
<div class="flex gap-1 flex-wrap text-xs">
<div class="flex flex-wrap gap-1 text-xs">
<div>
Created
<DateTime :date="item?.createdAt" />
@@ -491,12 +495,12 @@
</div>
</header>
<div class="divider my-0 mb-1"></div>
<div class="p-1 prose max-w-[100%]">
<div class="prose max-w-full p-1">
<Markdown v-if="item && item.description" class="text-base" :source="item.description"> </Markdown>
</div>
</div>
<div class="flex flex-wrap items-center justify-between mb-6 mt-3">
<div class="mb-6 mt-3 flex flex-wrap items-center justify-between">
<div class="btn-group">
<NuxtLink
v-for="t in tabs"
@@ -516,7 +520,7 @@
<BaseCard v-if="!hasNested" collapsable>
<template #title> Details </template>
<template #title-actions>
<div class="flex flex-wrap justify-between items-center mt-2 gap-4">
<div class="mt-2 flex flex-wrap items-center justify-between gap-4">
<label class="label cursor-pointer">
<input v-model="preferences.showEmpty" type="checkbox" class="toggle toggle-primary" />
<span class="label-text ml-4"> Show Empty </span>
@@ -528,13 +532,13 @@
<template #quantity="{ detail }">
{{ detail.text }}
<span
class="opacity-0 group-hover:opacity-100 ml-4 my-0 duration-75 transition-opacity inline-flex gap-2"
class="my-0 ml-4 inline-flex gap-2 opacity-0 transition-opacity duration-75 group-hover:opacity-100"
>
<button class="btn btn-circle btn-xs" @click="adjustQuantity(-1)">
<MdiMinus class="h-3 w-3" />
<MdiMinus class="size-3" />
</button>
<button class="btn btn-circle btn-xs" @click="adjustQuantity(1)">
<MdiPlus class="h-3 w-3" />
<MdiPlus class="size-3" />
</button>
</span>
</template>
@@ -546,10 +550,10 @@
<BaseCard v-if="photos && photos.length > 0">
<template #title> Photos </template>
<div
class="container border-t border-gray-300 p-4 flex flex-wrap gap-2 mx-auto max-h-[500px] overflow-y-scroll scroll-bg"
class="scroll-bg container mx-auto flex max-h-[500px] flex-wrap gap-2 overflow-y-scroll border-t border-gray-300 p-4"
>
<button v-for="(img, i) in photos" :key="i" @click="openDialog(img)">
<img class="rounded max-h-[200px]" :src="img.src" />
<img class="max-h-[200px] rounded" :src="img.src" />
</button>
</div>
</BaseCard>
@@ -587,7 +591,7 @@
</template>
</DetailsSection>
<div v-else>
<p class="text-base-content/70 px-6 pb-4">No attachments found</p>
<p class="px-6 pb-4 text-base-content/70">No attachments found</p>
</div>
</BaseCard>

View File

@@ -425,7 +425,7 @@
name="text"
:items="attachmentOpts"
/>
<div v-if="editState.type == 'photo'" class="flex gap-2 mt-3">
<div v-if="editState.type == 'photo'" class="mt-3 flex gap-2">
<input v-model="editState.primary" type="checkbox" class="checkbox" />
<p class="text-sm">
<span class="font-semibold">Primary Photo</span>
@@ -439,9 +439,9 @@
</BaseModal>
<section class="relative">
<div class="my-4 justify-end flex gap-2 items-center sticky z-10 top-1">
<div class="mr-auto tooltip tooltip-right" data-tip="Show Advanced View Options">
<label class="label cursor-pointer mr-auto">
<div class="sticky top-1 z-10 my-4 flex items-center justify-end gap-2">
<div class="tooltip tooltip-right mr-auto" data-tip="Show Advanced View Options">
<label class="label mr-auto cursor-pointer">
<input v-model="preferences.editorAdvancedView" type="checkbox" class="toggle toggle-primary" />
<span class="label-text ml-4"> Advanced </span>
</label>
@@ -452,7 +452,7 @@
</template>
Save
</BaseButton>
<BaseButton class="btn btn-sm btn-error" @click="deleteItem()">
<BaseButton class="btn btn-error btn-sm" @click="deleteItem()">
<MdiDelete class="mr-2" />
Delete
</BaseButton>
@@ -461,9 +461,9 @@
<BaseCard class="overflow-visible">
<template #title> Edit Details </template>
<template #title-actions>
<div class="flex flex-wrap justify-between items-center mt-2 gap-4"></div>
<div class="mt-2 flex flex-wrap items-center justify-between gap-4"></div>
</template>
<div class="px-5 pt-2 border-t mb-6 grid md:grid-cols-2 gap-4">
<div class="mb-6 grid gap-4 border-t px-5 pt-2 md:grid-cols-2">
<LocationSelector v-model="item.location" />
<FormMultiselect v-model="item.labels" label="Labels" :items="labels ?? []" />
<Autocomplete
@@ -478,8 +478,8 @@
</div>
<div class="border-t border-gray-300 sm:p-0">
<div v-for="field in mainFields" :key="field.ref" class="sm:divide-y sm:divide-gray-300 grid grid-cols-1">
<div class="pt-2 px-4 pb-4 sm:px-6 border-b border-gray-300">
<div v-for="field in mainFields" :key="field.ref" class="grid grid-cols-1 sm:divide-y sm:divide-gray-300">
<div class="border-b border-gray-300 px-4 pb-4 pt-2 sm:px-6">
<FormTextArea v-if="field.type === 'textarea'" v-model="item[field.ref]" :label="field.label" inline />
<FormTextField
v-else-if="field.type === 'text'"
@@ -513,25 +513,25 @@
<BaseCard>
<template #title> Custom Fields </template>
<div class="px-5 border-t divide-y divide-gray-300 space-y-4">
<div class="space-y-4 divide-y divide-gray-300 border-t px-5">
<div
v-for="(field, idx) in item.fields"
:key="`field-${idx}`"
class="grid grid-cols-2 md:grid-cols-4 gap-2"
class="grid grid-cols-2 gap-2 md:grid-cols-4"
>
<!-- <FormSelect v-model:value="field.type" label="Field Type" :items="fieldTypes" value-key="value" /> -->
<FormTextField v-model="field.name" label="Name" />
<div class="flex items-end col-span-3">
<div class="col-span-3 flex items-end">
<FormTextField v-model="field.textValue" label="Value" />
<div class="tooltip" data-tip="Delete">
<button class="btn btn-sm btn-square mb-2 ml-2" @click="item.fields.splice(idx, 1)">
<button class="btn btn-square btn-sm mb-2 ml-2" @click="item.fields.splice(idx, 1)">
<MdiDelete />
</button>
</div>
</div>
</div>
</div>
<div class="px-5 pb-4 mt-4 flex justify-end">
<div class="mt-4 flex justify-end px-5 pb-4">
<BaseButton size="sm" @click="addField"> Add </BaseButton>
</div>
</BaseCard>
@@ -539,7 +539,7 @@
<div
v-if="preferences.editorAdvancedView"
ref="attDropZone"
class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg"
class="card overflow-visible bg-base-100 shadow-xl sm:rounded-lg"
>
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg font-medium leading-6">Attachments</h3>
@@ -555,7 +555,7 @@
</div>
<button
v-else
class="h-24 w-full border-2 border-primary border-dashed grid place-content-center"
class="grid h-24 w-full place-content-center border-2 border-dashed border-primary"
@click="clickUpload"
>
<input ref="refAttachmentInput" hidden type="file" @change="uploadImage" />
@@ -570,20 +570,20 @@
:key="attachment.id"
class="grid grid-cols-6 justify-between py-3 pl-3 pr-4 text-sm"
>
<p class="my-auto col-span-4">
<p class="col-span-4 my-auto">
{{ attachment.document.title }}
</p>
<p class="my-auto">
{{ capitalize(attachment.type) }}
</p>
<div class="flex gap-2 justify-end">
<div class="flex justify-end gap-2">
<div class="tooltip" data-tip="Delete">
<button class="btn btn-sm btn-square" @click="deleteAttachment(attachment.id)">
<button class="btn btn-square btn-sm" @click="deleteAttachment(attachment.id)">
<MdiDelete />
</button>
</div>
<div class="tooltip" data-tip="Edit">
<button class="btn btn-sm btn-square" @click="openAttachmentEditDialog(attachment)">
<button class="btn btn-square btn-sm" @click="openAttachmentEditDialog(attachment)">
<MdiPencil />
</button>
</div>
@@ -593,7 +593,7 @@
</div>
</div>
<div v-if="preferences.editorAdvancedView" class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg">
<div v-if="preferences.editorAdvancedView" class="card overflow-visible bg-base-100 shadow-xl sm:rounded-lg">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg font-medium leading-6">Purchase Details</h3>
</div>
@@ -601,9 +601,9 @@
<div
v-for="field in purchaseFields"
:key="field.ref"
class="sm:divide-y sm:divide-gray-300 grid grid-cols-1"
class="grid grid-cols-1 sm:divide-y sm:divide-gray-300"
>
<div class="pt-2 px-4 pb-4 sm:px-6 border-b border-gray-300">
<div class="border-b border-gray-300 px-4 pb-4 pt-2 sm:px-6">
<FormTextArea v-if="field.type === 'textarea'" v-model="item[field.ref]" :label="field.label" inline />
<FormTextField
v-else-if="field.type === 'text'"
@@ -635,7 +635,7 @@
</div>
</div>
<div v-if="preferences.editorAdvancedView" class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg">
<div v-if="preferences.editorAdvancedView" class="card overflow-visible bg-base-100 shadow-xl sm:rounded-lg">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg font-medium leading-6">Warranty Details</h3>
</div>
@@ -643,9 +643,9 @@
<div
v-for="field in warrantyFields"
:key="field.ref"
class="sm:divide-y sm:divide-gray-300 grid grid-cols-1"
class="grid grid-cols-1 sm:divide-y sm:divide-gray-300"
>
<div class="pt-2 px-4 pb-4 sm:px-6 border-b border-gray-300">
<div class="border-b border-gray-300 px-4 pb-4 pt-2 sm:px-6">
<FormTextArea v-if="field.type === 'textarea'" v-model="item[field.ref]" :label="field.label" inline />
<FormTextField
v-else-if="field.type === 'text'"
@@ -677,13 +677,13 @@
</div>
</div>
<div v-if="preferences.editorAdvancedView" class="overflow-visible card bg-base-100 shadow-xl sm:rounded-lg">
<div v-if="preferences.editorAdvancedView" class="card overflow-visible bg-base-100 shadow-xl sm:rounded-lg">
<div class="px-4 py-5 sm:px-6">
<h3 class="text-lg font-medium leading-6">Sold Details</h3>
</div>
<div class="border-t border-gray-300 sm:p-0">
<div v-for="field in soldFields" :key="field.ref" class="sm:divide-y sm:divide-gray-300 grid grid-cols-1">
<div class="pt-2 pb-4 px-4 sm:px-6 border-b border-gray-300">
<div v-for="field in soldFields" :key="field.ref" class="grid grid-cols-1 sm:divide-y sm:divide-gray-300">
<div class="border-b border-gray-300 px-4 pb-4 pt-2 sm:px-6">
<FormTextArea v-if="field.type === 'textarea'" v-model="item[field.ref]" :label="field.label" inline />
<FormTextField
v-else-if="field.type === 'text'"

View File

@@ -188,7 +188,7 @@
<DatePicker v-model="entry.scheduledDate" label="Scheduled Date" />
<FormTextArea v-model="entry.description" label="Notes" />
<FormTextField v-model="entry.cost" autofocus label="Cost" />
<div class="py-2 flex justify-end">
<div class="flex justify-end py-2">
<BaseButton type="submit" class="ml-2 mt-2">
<template #icon>
<MdiPost />
@@ -200,11 +200,11 @@
</BaseModal>
<section class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
<StatCard
v-for="stat in stats"
:key="stat.id"
class="stats block shadow-xl border-l-primary"
class="stats block border-l-primary shadow-xl"
:title="stat.title"
:value="stat.value"
:type="stat.type"
@@ -228,7 +228,7 @@
</div>
<div class="container space-y-6">
<BaseCard v-for="e in log.entries" :key="e.id">
<BaseSectionHeader class="p-6 border-b border-b-gray-300">
<BaseSectionHeader class="border-b border-b-gray-300 p-6">
<span class="text-base-content">
{{ e.name }}
</span>
@@ -253,7 +253,7 @@
<div class="p-6">
<Markdown :source="e.description" />
</div>
<div class="flex justify-end p-4 gap-1">
<div class="flex justify-end gap-1 p-4">
<BaseButton size="sm" @click="openEditDialog(e)">
<template #icon>
<MdiEdit />
@@ -274,7 +274,7 @@
class="relative block w-full rounded-lg border-2 border-dashed border-base-content p-12 text-center"
@click="newEntry()"
>
<MdiWrenchClock class="h-16 w-16 inline" />
<MdiWrenchClock class="inline size-16" />
<span class="mt-2 block text-sm font-medium text-gray-900"> Create Your First Entry </span>
</button>
</div>

View File

@@ -40,9 +40,9 @@
<template>
<BaseContainer cmp="section">
<BaseSectionHeader> Add an Item To Your Inventory </BaseSectionHeader>
<form class="max-w-3xl mx-auto my-5 space-y-6" @submit.prevent="submit">
<div class="divider collapse-title px-0 cursor-pointer">Required Information</div>
<div class="bg-base-200 card">
<form class="mx-auto my-5 max-w-3xl space-y-6" @submit.prevent="submit">
<div class="collapse-title divider cursor-pointer px-0">Required Information</div>
<div class="card bg-base-200">
<div class="card-body">
<FormTextField v-model="form.name" label="Name" />
<FormTextArea v-model="form.description" label="Description" limit="1000" />
@@ -76,7 +76,7 @@
</div>
<div v-if="show.sold" class="card bg-base-200">
<div class="card-body">
<div class="grid md:grid-cols-2 gap-2">
<div class="grid gap-2 md:grid-cols-2">
<FormTextField v-model="form.soldTime" label="Sold Time" />
<FormTextField v-model="form.soldPrice" label="Sold Price" />
<FormTextField v-model="form.soldTo" label="Sold To" />

View File

@@ -40,6 +40,7 @@
const includeArchived = useRouteQuery("archived", false);
const fieldSelector = useRouteQuery("fieldSelector", false);
const negateLabels = useRouteQuery("negateLabels", false);
const orderBy = useRouteQuery("orderBy", "name");
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const hasNext = computed(() => page.value * pageSize.value < total.value);
@@ -157,6 +158,12 @@
return data;
});
watch(includeArchived, (newV, oldV) => {
if (newV !== oldV) {
search();
}
});
watch(fieldSelector, (newV, oldV) => {
if (newV === false && oldV === true) {
fieldTuples.value = [];
@@ -169,6 +176,12 @@
}
});
watch(orderBy, (newV, oldV) => {
if (newV !== oldV) {
search();
}
});
async function fetchValues(field: string): Promise<string[]> {
if (fieldValuesCache.value[field]) {
return fieldValuesCache.value[field];
@@ -201,6 +214,7 @@
pageSize: pageSize.value,
includeArchived: includeArchived.value ? "true" : "false",
negateLabels: negateLabels.value ? "true" : "false",
orderBy: orderBy.value,
},
});
}
@@ -231,6 +245,7 @@
includeArchived: includeArchived.value,
page: page.value,
pageSize: pageSize.value,
orderBy: orderBy.value,
fields,
});
@@ -278,6 +293,7 @@
archived: includeArchived.value ? "true" : "false",
fieldSelector: fieldSelector.value ? "true" : "false",
negateLabels: negateLabels.value ? "true" : "false",
orderBy: orderBy.value,
pageSize: pageSize.value,
page: page.value,
q: query.value,
@@ -311,6 +327,7 @@
fieldSelector: "false",
pageSize: 10,
page: 1,
orderBy: "name",
q: "",
loc: [],
lab: [],
@@ -325,11 +342,11 @@
<template>
<BaseContainer class="mb-16">
<div v-if="locations && labels">
<div class="flex flex-wrap md:flex-nowrap gap-4 items-end">
<div class="flex flex-wrap items-end gap-4 md:flex-nowrap">
<div class="w-full">
<FormTextField v-model="query" placeholder="Search" />
<div v-if="byAssetId" class="text-sm pl-2 pt-2">
<p>Querying Asset ID Number: {{ parsedAssetId }}</p>
<FormTextField v-model="query" :placeholder="$t('global.search')" />
<div v-if="byAssetId" class="pl-2 pt-2 text-sm">
<p>{{ $t("items.query_id", { id: parsedAssetId }) }}</p>
</div>
</div>
<BaseButton class="btn-block md:w-auto" @click.prevent="submit">
@@ -337,69 +354,77 @@
<MdiLoading v-if="loading" class="animate-spin" />
<MdiMagnify v-else />
</template>
Search
{{ $t("global.search") }}
</BaseButton>
</div>
<div class="flex flex-wrap md:flex-nowrap gap-2 w-full py-2">
<SearchFilter v-model="selectedLocations" label="Locations" :options="locationFlatTree">
<div class="flex w-full flex-wrap gap-2 py-2 md:flex-nowrap">
<SearchFilter v-model="selectedLocations" :label="$t('global.locations')" :options="locationFlatTree">
<template #display="{ item }">
<div>
<div class="flex w-full">
{{ item.name }}
</div>
<div v-if="item.name != item.treeString" class="text-xs mt-1">
<div v-if="item.name != item.treeString" class="mt-1 text-xs">
{{ item.treeString }}
</div>
</div>
</template>
</SearchFilter>
<SearchFilter v-model="selectedLabels" label="Labels" :options="labels" />
<SearchFilter v-model="selectedLabels" :label="$t('global.labels')" :options="labels" />
<div class="dropdown">
<label tabindex="0" class="btn btn-xs">Options</label>
<label tabindex="0" class="btn btn-xs">{{ $t("items.options") }}</label>
<div
tabindex="0"
class="dropdown-content mt-1 max-h-72 p-4 w-64 overflow-auto shadow bg-base-100 rounded-md -translate-x-24"
class="dropdown-content mt-1 max-h-72 w-64 -translate-x-24 overflow-auto rounded-md bg-base-100 p-4 shadow"
>
<label class="label cursor-pointer mr-auto">
<input v-model="includeArchived" type="checkbox" class="toggle toggle-sm toggle-primary" />
<span class="label-text ml-4"> Include Archived Items </span>
<label class="label mr-auto cursor-pointer">
<input v-model="includeArchived" type="checkbox" class="toggle toggle-primary toggle-sm" />
<span class="label-text ml-4"> {{ $t("items.include_archive") }} </span>
</label>
<label class="label cursor-pointer mr-auto">
<input v-model="fieldSelector" type="checkbox" class="toggle toggle-sm toggle-primary" />
<span class="label-text ml-4"> Field Selector </span>
<label class="label mr-auto cursor-pointer">
<input v-model="fieldSelector" type="checkbox" class="toggle toggle-primary toggle-sm" />
<span class="label-text ml-4"> {{ $t("items.field_selector") }} </span>
</label>
<label class="label cursor-pointer mr-auto">
<input v-model="negateLabels" type="checkbox" class="toggle toggle-sm toggle-primary" />
<span class="label-text ml-4"> Negate selected labels </span>
<label class="label mr-auto cursor-pointer">
<input v-model="negateLabels" type="checkbox" class="toggle toggle-primary toggle-sm" />
<span class="label-text ml-4"> {{ $t("items.negate_labels") }} </span>
</label>
<label class="label mr-auto cursor-pointer">
<select v-model="orderBy" class="select select-bordered select-sm">
<option value="name" selected>{{ $t("global.name") }}</option>
<option value="createdAt">{{ $t("items.created_at") }}</option>
<option value="updatedAt">{{ $t("items.updated_at") }}</option>
</select>
<span class="label-text ml-4"> {{ $t("items.order_by") }} </span>
</label>
<hr class="my-2" />
<BaseButton class="btn-block btn-sm" @click="reset"> Reset Search</BaseButton>
<BaseButton class="btn-sm btn-block" @click="reset"> {{ $t("items.reset_search") }} </BaseButton>
</div>
</div>
<div class="dropdown ml-auto dropdown-end">
<label tabindex="0" class="btn btn-xs">Tips</label>
<div class="dropdown dropdown-end ml-auto">
<label tabindex="0" class="btn btn-xs">{{ $t("items.tips") }}</label>
<div
tabindex="0"
class="dropdown-content mt-1 p-4 w-[325px] text-sm overflow-auto shadow bg-base-100 rounded-md"
class="dropdown-content mt-1 w-[325px] overflow-auto rounded-md bg-base-100 p-4 text-sm shadow"
>
<p class="text-base">Search Tips</p>
<p class="text-base">{{ $t("items.tips_sub") }}</p>
<ul class="mt-1 list-disc pl-6">
<li>
Location and label filters use the 'OR' operation. If more than one is selected only one will be
required for a match.
{{ $t("items.tip_1") }}
</li>
<li>Searches prefixed with '#'' will query for a asset ID (example '#000-001')</li>
<li>
Field filters use the 'OR' operation. If more than one is selected only one will be required for a
match.
{{ $t("items.tip_2") }}
</li>
<li>
{{ $t("items.tip_3") }}
</li>
</ul>
</div>
</div>
</div>
<div v-if="fieldSelector" class="py-4 space-y-2">
<p>Custom Fields</p>
<div v-if="fieldSelector" class="space-y-2 py-4">
<p>{{ $t("items.custom_fields") }}</p>
<div v-for="(f, idx) in fieldTuples" :key="idx" class="flex flex-wrap gap-2">
<div class="form-control w-full max-w-xs">
<label class="label">
@@ -407,7 +432,7 @@
</label>
<select
v-model="fieldTuples[idx][0]"
class="select-bordered select"
class="select select-bordered"
:items="allFields ?? []"
@change="fetchValues(f[0])"
>
@@ -416,52 +441,56 @@
</div>
<div class="form-control w-full max-w-xs">
<label class="label">
<span class="label-text">Field Value</span>
<span class="label-text">{{ $t("items.field_value") }}</span>
</label>
<select v-model="fieldTuples[idx][1]" class="select-bordered select" :items="fieldValuesCache[f[0]]">
<select v-model="fieldTuples[idx][1]" class="select select-bordered" :items="fieldValuesCache[f[0]]">
<option v-for="v in fieldValuesCache[f[0]]" :key="v" :value="v">{{ v }}</option>
</select>
</div>
<button
type="button"
class="btn btn-square btn-sm md:ml-0 ml-auto mt-auto mb-2"
class="btn btn-square btn-sm mb-2 ml-auto mt-auto md:ml-0"
@click="fieldTuples.splice(idx, 1)"
>
<MdiDelete class="w-5 h-5" />
<MdiDelete class="size-5" />
</button>
</div>
<BaseButton type="button" class="btn-sm mt-2" @click="() => fieldTuples.push(['', ''])"> Add</BaseButton>
<BaseButton type="button" class="btn-sm mt-2" @click="() => fieldTuples.push(['', ''])">
{{ $t("items.add") }}
</BaseButton>
</div>
</div>
<section class="mt-10">
<BaseSectionHeader ref="itemsTitle"> Items </BaseSectionHeader>
<p class="text-base font-medium flex items-center">
{{ total }} Results
<span class="text-base ml-auto"> Page {{ page }} of {{ totalPages }}</span>
<BaseSectionHeader ref="itemsTitle"> {{ $t("global.items") }} </BaseSectionHeader>
<p class="flex items-center text-base font-medium">
{{ $t("items.results", { total: total }) }}
<span class="ml-auto text-base"> {{ $t("items.pages", { page: page, totalPages: totalPages }) }} </span>
</p>
<div ref="cardgrid" class="grid mt-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<div ref="cardgrid" class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
<ItemCard v-for="item in items" :key="item.id" :item="item" />
<div class="hidden first:inline text-xl">No Items Found</div>
<div class="hidden text-xl first:inline">{{ $t("items.no_results") }}</div>
</div>
<div v-if="items.length > 0 && (hasNext || hasPrev)" class="mt-10 flex gap-2 flex-col items-center">
<div v-if="items.length > 0 && (hasNext || hasPrev)" class="mt-10 flex flex-col items-center gap-2">
<div class="flex">
<div class="btn-group">
<button :disabled="!hasPrev" class="btn text-no-transform" @click="prev">
<MdiChevronLeft class="mr-1 h-6 w-6" name="mdi-chevron-left" />
Prev
<button :disabled="!hasPrev" class="text-no-transform btn" @click="prev">
<MdiChevronLeft class="mr-1 size-6" name="mdi-chevron-left" />
{{ $t("items.prev_page") }}
</button>
<button v-if="hasPrev" class="btn text-no-transform" @click="page = 1">First</button>
<button v-if="hasNext" class="btn text-no-transform" @click="page = totalPages">Last</button>
<button :disabled="!hasNext" class="btn text-no-transform" @click="next">
Next
<MdiChevronRight class="ml-1 h-6 w-6" name="mdi-chevron-right" />
<button v-if="hasPrev" class="text-no-transform btn" @click="page = 1">{{ $t("items.first") }}</button>
<button v-if="hasNext" class="text-no-transform btn" @click="page = totalPages">
{{ $t("items.last") }}
</button>
<button :disabled="!hasNext" class="text-no-transform btn" @click="next">
{{ $t("items.next_page") }}
<MdiChevronRight class="ml-1 size-6" name="mdi-chevron-right" />
</button>
</div>
</div>
<p class="text-sm font-bold">Page {{ page }} of {{ totalPages }}</p>
<p class="text-sm font-bold">{{ $t("items.pages", { page: page, totalPages: totalPages }) }}</p>
</div>
</section>
</BaseContainer>

View File

@@ -106,28 +106,28 @@
</BaseModal>
<BaseContainer v-if="label">
<div class="bg-base-100 rounded p-3">
<div class="rounded bg-base-100 p-3">
<header class="mb-2">
<div class="flex flex-wrap items-end gap-2">
<div class="avatar placeholder mb-auto">
<div class="bg-neutral-focus text-neutral-content rounded-full w-12">
<MdiPackageVariant class="h-7 w-7" />
<div class="w-12 rounded-full bg-neutral-focus text-neutral-content">
<MdiPackageVariant class="size-7" />
</div>
</div>
<div>
<h1 class="text-2xl pb-1 flex items-center gap-3">
<h1 class="flex items-center gap-3 pb-1 text-2xl">
{{ label ? label.name : "" }}
<div
v-if="items && items.totalPrice"
class="text-xs bg-secondary text-secondary-content rounded-full px-2 py-1"
class="rounded-full bg-secondary px-2 py-1 text-xs text-secondary-content"
>
<div>
<Currency :amount="items.totalPrice" />
</div>
</div>
</h1>
<div class="flex gap-1 flex-wrap text-xs">
<div class="flex flex-wrap gap-1 text-xs">
<div>
Created
<DateTime :date="label?.createdAt" />

Some files were not shown because too many files have changed in this diff Show More