Compare commits

..

163 Commits

Author SHA1 Message Date
Matt Kilgore
f386d1213f Update TextField.vue 2024-10-04 16:08:20 -04:00
Matt Kilgore
9b7b00e8f2 fix: Nuxt can't be fully updated 2024-10-04 14:54:34 -04:00
Matt Kilgore
055f0219a8 chore: update nuxt 2024-10-04 14:41:36 -04:00
Matt Kilgore
9d3f3cf1da fix: #232 wrap text fields for from and to fields for sold and purchased. 2024-10-04 14:38:29 -04:00
Matt Kilgore
da8cc19838 fix: #204 sets inline form inputs to use properties correctly, numbers now enforce correct format. 2024-10-04 14:24:35 -04:00
Matt Kilgore
a3d5485c1d fix: #147 notifier now shows previous input value 2024-10-04 13:49:36 -04:00
Matt Kilgore
865661097c fix: #181 MultiSelect can now be cleared manually. 2024-10-04 13:31:06 -04:00
Weblate
ab48f55335 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (176 of 176 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (176 of 176 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (176 of 176 strings)

Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.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/pt_BR/
Translation: Homebox/Frontend
2024-10-02 12:58:06 +00:00
Weblate
49f52cada4 Translated using Weblate (Portuguese (Brazil))
Currently translated at 93.1% (164 of 176 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 93.1% (164 of 176 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 93.1% (164 of 176 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.2% (173 of 176 strings)

Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: Jackxwb <xwb9606@163.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/pt_BR/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-10-01 12:45:24 +00:00
Weblate
059bc5f16c Translated using Weblate (Chinese (Simplified))
Currently translated at 98.2% (173 of 176 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.2% (173 of 176 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.2% (173 of 176 strings)

Co-authored-by: Jackxwb <xwb9606@163.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/zh_Hans/
Translation: Homebox/Frontend
2024-10-01 04:28:45 +00:00
Weblate
a8eab724f9 Translated using Weblate (Catalan)
Currently translated at 72.7% (128 of 176 strings)

Translated using Weblate (Catalan)

Currently translated at 72.7% (128 of 176 strings)

Translated using Weblate (Catalan)

Currently translated at 72.7% (128 of 176 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 86.9% (153 of 176 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 86.9% (153 of 176 strings)

Translated using Weblate (French)

Currently translated at 99.4% (175 of 176 strings)

Translated using Weblate (French)

Currently translated at 99.4% (175 of 176 strings)

Translated using Weblate (Dutch)

Currently translated at 96.0% (169 of 176 strings)

Translated using Weblate (Dutch)

Currently translated at 96.0% (169 of 176 strings)

Translated using Weblate (Dutch)

Currently translated at 96.0% (169 of 176 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: Jean-Philippe Baril <translate.sysadminsmedia.com@alias.trebaxis.net>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
Co-authored-by: Xavier Clotet <x.clotetfons@gmail.com>
Co-authored-by: mcarbonne <maximilien.carbonne@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ca/
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/zh_Hans/
Translation: Homebox/Frontend
2024-10-01 04:16:11 +00:00
Weblate
eed967e9b4 Translated using Weblate (Hungarian)
Currently translated at 100.0% (176 of 176 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (176 of 176 strings)

Translated using Weblate (Catalan)

Currently translated at 63.0% (111 of 176 strings)

Translated using Weblate (Catalan)

Currently translated at 63.0% (111 of 176 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (176 of 176 strings)

Translated using Weblate (Slovenian)

Currently translated at 100.0% (176 of 176 strings)

Translated using Weblate (Dutch)

Currently translated at 81.8% (144 of 176 strings)

Translated using Weblate (Dutch)

Currently translated at 81.8% (144 of 176 strings)

Translated using Weblate (Dutch)

Currently translated at 81.8% (144 of 176 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
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/hu/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/sl/
Translation: Homebox/Frontend
2024-09-30 13:29:46 +00:00
Katos
2eb421455b Merge pull request #137 from strass/label-report-prefill
label generation tool prefills already allocated items
2024-09-30 13:15:09 +01:00
Weblate
e0d86ce745 Translated using Weblate (Slovenian)
Currently translated at 90.3% (159 of 176 strings)

Translated using Weblate (Slovenian)

Currently translated at 90.3% (159 of 176 strings)

Translated using Weblate (Slovenian)

Currently translated at 90.3% (159 of 176 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
Co-authored-by: thehijacker <thehijacker@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/sl/
Translation: Homebox/Frontend
2024-09-30 04:33:44 +00:00
Weblate
ba8f32cec8 Translated using Weblate (Slovenian)
Currently translated at 61.3% (108 of 176 strings)

Translated using Weblate (Slovenian)

Currently translated at 61.3% (108 of 176 strings)

Translated using Weblate (German)

Currently translated at 100.0% (176 of 176 strings)

Translated using Weblate (German)

Currently translated at 100.0% (176 of 176 strings)

Co-authored-by: Gurke090 <leo.hellfach@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: thehijacker <thehijacker@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/sl/
Translation: Homebox/Frontend
2024-09-30 04:22:40 +00:00
Weblate
4af6bf210e Translated using Weblate (German)
Currently translated at 99.4% (173 of 174 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translation: Homebox/Frontend
2024-09-28 20:35:54 +00:00
Katos
1de3ecda19 Merge pull request #234 from tonyaellie/add-char-counts-with-max-to-lots-of-inputs
Add char counters to inputs
2024-09-28 21:35:50 +01:00
Tonya
4a0039c838 Merge branch 'main' into add-char-counts-with-max-to-lots-of-inputs 2024-09-28 20:28:01 +00:00
Katos
02a34ed1a0 Merge pull request #246 from mcarbonne/feat_maintenance_complete_and_duplicate
Feature: improve maintenance view with new actions
2024-09-28 19:45:45 +01:00
Weblate
dc23a1ae23 Translated using Weblate (German)
Currently translated at 99.4% (173 of 174 strings)

Translated using Weblate (German)

Currently translated at 99.4% (173 of 174 strings)

Translated using Weblate (German)

Currently translated at 99.4% (173 of 174 strings)

Translated using Weblate (German)

Currently translated at 99.4% (173 of 174 strings)

Translated using Weblate (German)

Currently translated at 99.4% (173 of 174 strings)

Co-authored-by: Gurke090 <leo.hellfach@gmail.com>
Co-authored-by: Maxklos <herzognikolaus@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Nic <nicmeier1@gmx.net>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translation: Homebox/Frontend
2024-09-28 14:11:57 +00:00
Weblate
4fea927880 Translated using Weblate (German)
Currently translated at 95.9% (167 of 174 strings)

Translated using Weblate (German)

Currently translated at 95.9% (167 of 174 strings)

Co-authored-by: Gurke090 <leo.hellfach@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translation: Homebox/Frontend
2024-09-28 14:01:24 +00:00
Maximilien Carbonne
ff874ac472 fix: wrong return type 2024-09-27 21:38:27 +02:00
Maximilien Carbonne
ba48a615cd minor fixes (style) 2024-09-27 21:38:27 +02:00
Maximilien Carbonne
5aa389b13c always show duplicate action 2024-09-27 21:38:27 +02:00
Maximilien Carbonne
b500f6b51f minor fix 2024-09-27 21:38:27 +02:00
Maximilien Carbonne
2ea9ed0476 keep only basic actions (duplicate and mark as done) 2024-09-27 21:38:27 +02:00
Maximilien Carbonne
68ee701e46 fix: action buttons cropped on mobile devices 2024-09-27 21:38:27 +02:00
Maximilien Carbonne
dc88406bcc rework frontend for maintenances (factorize) 2024-09-27 21:38:27 +02:00
Maximilien Carbonne
ca85d4b483 simplify/factorize backend for maintenance 2024-09-27 21:38:27 +02:00
Maximilien Carbonne
17ecfa2c66 maintenances: add new actions 2024-09-27 21:38:26 +02:00
mcarbonne
cabaa07384 fix close & download buttons when viewing a picture attachment (#239) 2024-09-26 13:44:55 -04:00
Weblate
d9b05e872c Translated using Weblate (Hungarian)
Currently translated at 100.0% (174 of 174 strings)

Translated using Weblate (Slovenian)

Currently translated at 59.1% (103 of 174 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: stegl <primsteg@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/sl/
Translation: Homebox/Frontend
2024-09-26 10:35:45 +00:00
Weblate
bd60c36240 Translated using Weblate (Hungarian)
Currently translated at 100.0% (174 of 174 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (174 of 174 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (174 of 174 strings)

Translated using Weblate (French)

Currently translated at 99.4% (173 of 174 strings)

Translated using Weblate (French)

Currently translated at 99.4% (173 of 174 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
Co-authored-by: mcarbonne <maximilien.carbonne@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-25 20:47:09 +00:00
Weblate
1fc9843450 Translated using Weblate (English)
Currently translated at 99.4% (173 of 174 strings)

Translated using Weblate (German)

Currently translated at 88.8% (151 of 170 strings)

Translated using Weblate (German)

Currently translated at 88.8% (151 of 170 strings)

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/de/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/en/
Translation: Homebox/Frontend
2024-09-25 02:05:08 +00:00
Filipe Dobreira
4e260c5a8b feat: Fix, add, tweak various empty states across the app (#241)
* fix: add empty state to locations list

* fix: add empty states for homepage lists

* fix: add empty state to notifiers list

* fix: update profile notifiers to use translation, add en and pt-pt copy

* chore: tweak copy for notifier empty state

* fix: add new empty state for search page

* fix: update new empty states to use translation strings

* chore: eslint fixes, translation

* fix: translation key

---------

Co-authored-by: Matt Kilgore <matthew@kilgore.dev>
2024-09-24 08:28:54 -04:00
mcarbonne
55a9355046 fix "task go:all" (#238) 2024-09-23 14:50:02 -04:00
Weblate
5bee3dd429 Translated using Weblate (German)
Currently translated at 85.2% (145 of 170 strings)

Translated using Weblate (German)

Currently translated at 85.2% (145 of 170 strings)

Translated using Weblate (Russian)

Currently translated at 82.9% (117 of 141 strings)

Translated using Weblate (Russian)

Currently translated at 82.9% (117 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (141 of 141 strings)

Co-authored-by: Eugene Zrazhevsky <eugeny.zrazhevsky@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Nic <nicmeier1@gmx.net>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: alexdelli <alexdelli@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/it/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ru/
Translation: Homebox/Frontend
2024-09-23 18:47:53 +00:00
mcarbonne
a85c42b539 Implement maintenance view (#235)
* implement backend to query maintenances

* improve backend API for maintenances

* add itemId and itemName (v1/maintenances)

* fix remaining todo in backend (/v1/maintenances)

* refactor: extract MaintenanceEditModal

* first draft (to be cleaned)

* revert dependency updates (not required)

* translation + fix deletion

* fix main menu css issues

* fix main menu "create" button (css)

* enhancement: make item name clickable

* fix: add page title (+ translate existing ones)

* fix: missing toast translation (when updating)

* bug fix: missing group check in backend (for new endpoint)

* backport from following PR (to avoid useless changes)

* maintenances => maintenance
2024-09-23 13:07:27 -04:00
Filipe Dobreira
897f3842e0 fix: redirect back to locations list after deleting location (#240) 2024-09-23 08:12:59 -04:00
Gabrinator
791d390187 docs: add Organizing Your Items page (#236) 2024-09-23 08:12:44 -04:00
Filipe Dobreira
e85b1a44b1 fix: prevent submit modal when trying to create location with no name (#243) 2024-09-23 08:11:59 -04:00
Weblate
ffd59d535c Translated using Weblate (Italian)
Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (141 of 141 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: alexdelli <alexdelli@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/it/
Translation: Homebox/Frontend
2024-09-22 19:02:30 +00:00
Weblate
e32a1041b1 Translated using Weblate (Italian)
Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (141 of 141 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: alexdelli <alexdelli@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/it/
Translation: Homebox/Frontend
2024-09-22 19:00:43 +00:00
Weblate
c16269bcb4 Translated using Weblate (Italian)
Currently translated at 96.4% (136 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 96.4% (136 of 141 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: alexdelli <alexdelli@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/it/
Translation: Homebox/Frontend
2024-09-22 18:55:09 +00:00
Weblate
81d57d4c47 Translated using Weblate (Italian)
Currently translated at 95.7% (135 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 95.7% (135 of 141 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: alexdelli <alexdelli@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/it/
Translation: Homebox/Frontend
2024-09-22 18:55:00 +00:00
Weblate
bdb3b5dc24 Translated using Weblate (Italian)
Currently translated at 92.9% (131 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 92.9% (131 of 141 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: alexdelli <alexdelli@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/it/
Translation: Homebox/Frontend
2024-09-22 18:54:40 +00:00
Weblate
666903e663 Translated using Weblate (Russian)
Currently translated at 79.4% (112 of 141 strings)

Translated using Weblate (Russian)

Currently translated at 79.4% (112 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 92.1% (130 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 92.1% (130 of 141 strings)

Co-authored-by: Eugene Zrazhevsky <eugeny.zrazhevsky@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: alexdelli <alexdelli@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/it/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ru/
Translation: Homebox/Frontend
2024-09-22 18:54:12 +00:00
Weblate
24deb462a8 Translated using Weblate (Russian)
Currently translated at 68.0% (96 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 73.7% (104 of 141 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: alexdelli <alexdelli@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/it/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ru/
Translation: Homebox/Frontend
2024-09-22 17:25:53 +00:00
Weblate
85b54c4ccf Translated using Weblate (Italian)
Currently translated at 73.7% (104 of 141 strings)

Translated using Weblate (Italian)

Currently translated at 73.7% (104 of 141 strings)

Co-authored-by: Gabriele Bonetti <gabriele.bonetti@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/it/
Translation: Homebox/Frontend
2024-09-22 10:19:29 +00:00
Tonya
ca33d499ab fix: prevent DatePicker from being hidden when overflow is hidden on parent component (#237) 2024-09-20 15:58:50 -04:00
Weblate
52ebff7ca4 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (141 of 141 strings)

Co-authored-by: Daniel Zanardi de Souza <zz.uploader@gmail.com>
Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-19 14:58:06 +00:00
Weblate
4cf0b0a9df Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.2% (140 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.2% (140 of 141 strings)

Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 14:42:46 +00:00
Weblate
0d92d2718d Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.1% (137 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.1% (137 of 141 strings)

Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 14:42:26 +00:00
Weblate
22937dd314 Translated using Weblate (Portuguese (Brazil))
Currently translated at 85.1% (120 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 85.1% (120 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 85.1% (120 of 141 strings)

Co-authored-by: Daniel Zanardi de Souza <zz.uploader@gmail.com>
Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 14:37:14 +00:00
Weblate
26a99b6bad Translated using Weblate (Portuguese (Brazil))
Currently translated at 66.6% (94 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 66.6% (94 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 66.6% (94 of 141 strings)

Co-authored-by: Daniel Zanardi de Souza <zz.uploader@gmail.com>
Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 14:24:09 +00:00
Weblate
4bf7f3e00c Translated using Weblate (Portuguese (Brazil))
Currently translated at 59.5% (84 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 59.5% (84 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 59.5% (84 of 141 strings)

Co-authored-by: Daniel Zanardi de Souza <zz.uploader@gmail.com>
Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 14:16:41 +00:00
Weblate
269c5ea7e3 Translated using Weblate (Portuguese (Brazil))
Currently translated at 54.6% (77 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 54.6% (77 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 54.6% (77 of 141 strings)

Co-authored-by: Daniel Zanardi de Souza <zz.uploader@gmail.com>
Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 14:13:51 +00:00
Weblate
bac6212188 Translated using Weblate (Portuguese (Brazil))
Currently translated at 51.7% (73 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 51.7% (73 of 141 strings)

Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 14:12:41 +00:00
Weblate
7a7d00220a Translated using Weblate (Portuguese (Brazil))
Currently translated at 45.3% (64 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 45.3% (64 of 141 strings)

Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 14:03:52 +00:00
Weblate
a20f94b68a Translated using Weblate (Portuguese (Brazil))
Currently translated at 44.6% (63 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 44.6% (63 of 141 strings)

Co-authored-by: Daniel Zanardi de Souza <zz.uploader@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 14:02:44 +00:00
Weblate
7c4c373851 Translated using Weblate (Portuguese (Brazil))
Currently translated at 31.2% (44 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 31.2% (44 of 141 strings)

Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 13:57:44 +00:00
Weblate
8b0684a0ca Translated using Weblate (Portuguese (Brazil))
Currently translated at 30.4% (43 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 30.4% (43 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 30.4% (43 of 141 strings)

Co-authored-by: Daniel Zanardi de Souza <zz.uploader@gmail.com>
Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 13:55:49 +00:00
Weblate
c903a9df13 Translated using Weblate (Portuguese (Brazil))
Currently translated at 24.8% (35 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 24.8% (35 of 141 strings)

Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 13:54:03 +00:00
Weblate
a5d4e4f491 Translated using Weblate (Portuguese (Brazil))
Currently translated at 20.5% (29 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 20.5% (29 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 20.5% (29 of 141 strings)

Co-authored-by: Daniel Zanardi de Souza <zz.uploader@gmail.com>
Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 13:51:36 +00:00
Weblate
f3116e4729 Translated using Weblate (Portuguese (Brazil))
Currently translated at 12.7% (18 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 12.7% (18 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 12.7% (18 of 141 strings)

Co-authored-by: Daniel Zanardi de Souza <zz.uploader@gmail.com>
Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 13:49:53 +00:00
Weblate
c0b88142d8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 6.3% (9 of 141 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 6.3% (9 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (141 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: Daniel Zanardi de Souza <zz.uploader@gmail.com>
Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 13:46:20 +00:00
Weblate
19adc6a441 Translated using Weblate (Hungarian)
Currently translated at 99.2% (140 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 99.2% (140 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 13:42:22 +00:00
Weblate
d9fa7a846c Translated using Weblate (Portuguese (Brazil))
Currently translated at 0.7% (1 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 87.2% (123 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 87.2% (123 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: Gustavo Souza <gustavobat.gb@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_BR/
Translation: Homebox/Frontend
2024-09-18 13:37:27 +00:00
Weblate
aa30fba7af Translated using Weblate (Hungarian)
Currently translated at 79.4% (112 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 79.4% (112 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 13:18:32 +00:00
Weblate
edbb4b9fa0 Translated using Weblate (Hungarian)
Currently translated at 78.7% (111 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 78.7% (111 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 13:17:51 +00:00
Weblate
7d8534ffc1 Translated using Weblate (Hungarian)
Currently translated at 76.5% (108 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 76.5% (108 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 13:14:46 +00:00
Weblate
28b79beb76 Translated using Weblate (Hungarian)
Currently translated at 74.4% (105 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 74.4% (105 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 13:13:53 +00:00
Weblate
8a7af9a98d Translated using Weblate (Hungarian)
Currently translated at 73.0% (103 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 73.0% (103 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 13:07:04 +00:00
Weblate
47600ade0e Translated using Weblate (Hungarian)
Currently translated at 71.6% (101 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 71.6% (101 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 13:06:06 +00:00
Weblate
2af0cfeb49 Translated using Weblate (Hungarian)
Currently translated at 70.9% (100 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 70.9% (100 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 13:05:24 +00:00
Weblate
f6690dc80f Translated using Weblate (Hungarian)
Currently translated at 63.8% (90 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 63.8% (90 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 12:45:58 +00:00
Weblate
9de5cf8c58 Translated using Weblate (Hungarian)
Currently translated at 63.1% (89 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 63.1% (89 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 12:42:21 +00:00
Weblate
9e66bf0bc1 Translated using Weblate (Hungarian)
Currently translated at 60.2% (85 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 60.2% (85 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 12:40:49 +00:00
Weblate
5f27f5d9cc Translated using Weblate (Hungarian)
Currently translated at 56.7% (80 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 56.7% (80 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 12:38:05 +00:00
Weblate
0a1667ed24 Translated using Weblate (Hungarian)
Currently translated at 50.3% (71 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 50.3% (71 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 12:32:06 +00:00
Weblate
6348aeac6e Translated using Weblate (Hungarian)
Currently translated at 48.9% (69 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 48.9% (69 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 12:31:26 +00:00
Weblate
7e9bd7f44b Translated using Weblate (Hungarian)
Currently translated at 43.2% (61 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 43.2% (61 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 12:27:41 +00:00
Weblate
cf780393c2 Translated using Weblate (Hungarian)
Currently translated at 41.8% (59 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 41.8% (59 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 12:26:28 +00:00
Weblate
e94f436ba6 Translated using Weblate (Hungarian)
Currently translated at 34.0% (48 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 34.0% (48 of 141 strings)

Co-authored-by: Adam Kleizer <adamkleizer@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translation: Homebox/Frontend
2024-09-18 12:11:20 +00:00
Weblate
0c6be05b05 Translated using Weblate (Portuguese (Portugal))
Currently translated at 99.2% (140 of 141 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 99.2% (140 of 141 strings)

Translated using Weblate (Hungarian)

Currently translated at 34.0% (48 of 141 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Rui Silva <rds.mcc@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/hu/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_PT/
Translation: Homebox/Frontend
2024-09-18 11:57:48 +00:00
Weblate
9297ac59ae Translated using Weblate (Portuguese (Portugal))
Currently translated at 63.8% (90 of 141 strings)

Translated using Weblate (Portuguese (Portugal))

Currently translated at 63.8% (90 of 141 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Rui Silva <rds.mcc@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/pt_PT/
Translation: Homebox/Frontend
2024-09-18 10:27:48 +00:00
Weblate
983dedfdad Translated using Weblate (German)
Currently translated at 100.0% (141 of 141 strings)

Added translation using Weblate (Portuguese)

Co-authored-by: Nic <nicmeier1@gmx.net>
Co-authored-by: Rui Silva <rds.mcc@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translation: Homebox/Frontend
2024-09-18 09:54:07 +00:00
tonya
d159908b91 feat: add char counts 2024-09-17 21:58:57 +00:00
Weblate
1b53a5235c Translated using Weblate (Spanish)
Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (French)

Currently translated at 99.2% (140 of 141 strings)

Translated using Weblate (French)

Currently translated at 99.2% (140 of 141 strings)

Translated using Weblate (French)

Currently translated at 99.2% (140 of 141 strings)

Translated using Weblate (German)

Currently translated at 100.0% (141 of 141 strings)

Co-authored-by: CanbiZ <mickey.leskowitz@gmail.com>
Co-authored-by: Jean-Philippe Baril <translate.sysadminsmedia.com@alias.trebaxis.net>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: Slydite4 <39199098+Slydite4@users.noreply.github.com>
Co-authored-by: mcarbonne <maximilien.carbonne@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/de/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/es/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/fr/
Translation: Homebox/Frontend
2024-09-17 09:58:06 +00:00
mcarbonne
f3f709748e only accept common image formats (#227)
Co-authored-by: Matt Kilgore <tankerkiller125@users.noreply.github.com>
2024-09-16 21:14:00 -04:00
Tonya
64ceffefe9 show add photo button on mobile (#229)
* fix: show add photo button on mobile

* feat: improve on short wide devices
2024-09-16 15:05:51 -04:00
Matt Kilgore
4b24653b86 Better issue templates 2024-09-16 08:36:41 -04:00
Weblate
6a0ebb76ea Translated using Weblate (Spanish)
Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (141 of 141 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (141 of 141 strings)

Co-authored-by: Daniel Barea <dbarelop@gmail.com>
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-09-16 09:44:53 +00:00
Weblate
8e273730be Translated using Weblate (Spanish)
Currently translated at 85.1% (120 of 141 strings)

Translated using Weblate (Dutch)

Currently translated at 77.3% (109 of 141 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: Daniel Barea <dbarelop@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/es/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translation: Homebox/Frontend
2024-09-16 09:36:13 +00:00
Weblate
5c09953e4c Translated using Weblate (Dutch)
Currently translated at 77.3% (109 of 141 strings)

Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translation: Homebox/Frontend
2024-09-16 06:46:57 +00:00
Weblate
6866dc76c0 Translated using Weblate (Dutch)
Currently translated at 75.8% (107 of 141 strings)

Translated using Weblate (Dutch)

Currently translated at 75.8% (107 of 141 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translation: Homebox/Frontend
2024-09-16 06:45:56 +00:00
Weblate
de16f09108 Translated using Weblate (Dutch)
Currently translated at 72.3% (102 of 141 strings)

Translated using Weblate (Dutch)

Currently translated at 72.3% (102 of 141 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translation: Homebox/Frontend
2024-09-16 06:45:16 +00:00
Weblate
3f3ca345fd Translated using Weblate (Dutch)
Currently translated at 70.2% (99 of 141 strings)

Translated using Weblate (Dutch)

Currently translated at 70.2% (99 of 141 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translation: Homebox/Frontend
2024-09-16 06:43:51 +00:00
Weblate
9275d2db9c Translated using Weblate (Dutch)
Currently translated at 68.0% (96 of 141 strings)

Translated using Weblate (Dutch)

Currently translated at 68.0% (96 of 141 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translation: Homebox/Frontend
2024-09-16 06:43:14 +00:00
Weblate
d88b04b66f Translated using Weblate (Chinese (Simplified))
Currently translated at 98.5% (139 of 141 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.5% (139 of 141 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 98.5% (139 of 141 strings)

Translated using Weblate (Dutch)

Currently translated at 66.6% (94 of 141 strings)

Translated using Weblate (Dutch)

Currently translated at 66.6% (94 of 141 strings)

Co-authored-by: 101br03k <warmerdamm03@gmail.com>
Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: dongzh <whitefivefiveopen@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/nl/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-16 06:42:12 +00:00
Weblate
62e6b08baf Translated using Weblate (Chinese (Simplified))
Currently translated at 74.4% (105 of 141 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 74.4% (105 of 141 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 74.4% (105 of 141 strings)

Co-authored-by: Jackxwb <xwb9606@163.com>
Co-authored-by: MyMemory <noreply-mt-mymemory@weblate.org>
Co-authored-by: dongzh <whitefivefiveopen@gmail.com>
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/zh_Hans/
Translation: Homebox/Frontend
2024-09-16 02:06:10 +00:00
Weblate
6fa37cb474 Translated using Weblate (Chinese (Simplified))
Currently translated at 65.9% (93 of 141 strings)

Translated using Weblate (Russian)

Currently translated at 67.3% (95 of 141 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/zh_Hans/
Translation: Homebox/Frontend
2024-09-16 01:23:56 +00:00
Matt Kilgore
d63d6e94dd refactor: add doc head metadata 2024-09-14 23:15:51 -04:00
Matt Kilgore
0c8ce366eb refactor: move english menu to separate file. 2024-09-14 22:59:18 -04:00
Matt Kilgore
3da3025935 chore: Update translation contribution documentation. 2024-09-14 22:53:09 -04:00
Matt Kilgore
d1a57e3ec5 fix: ignore localhost in docs as dead link 2024-09-14 22:46:02 -04:00
Weblate
969ef1941b Translated using Weblate (Russian)
Currently translated at 65.2% (92 of 141 strings)

Translated using Weblate (Spanish)

Currently translated at 84.3% (119 of 141 strings)

Translated using Weblate (Spanish)

Currently translated at 84.3% (119 of 141 strings)

Translated using Weblate (German)

Currently translated at 80.1% (113 of 141 strings)

Co-authored-by: Kollskoya <haufey@outlook.de>
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/de/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/es/
Translate-URL: https://translate.sysadminsmedia.com/projects/homebox/frontend/ru/
Translation: Homebox/Frontend
2024-09-14 22:48:35 +00:00
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
Matt Kilgore
229d4db996 fix: types 2024-09-02 20:29:32 -04:00
zak
184be32f3a Merge branch 'main' into label-report-prefill 2024-09-01 22:03:59 -07:00
zak
8058743c2f Merge branch 'main' into label-report-prefill 2024-08-19 18:05:34 -07:00
zak
a6bdadedb1 Merge branch 'main' into label-report-prefill 2024-08-14 18:17:21 -07:00
zak
d08dafd965 Merge branch 'main' into label-report-prefill 2024-08-11 19:03:05 -07:00
zak
fef026ed47 Merge branch 'main' into label-report-prefill 2024-08-01 21:28:50 -07:00
zak
6001bf90c5 Merge branch 'main' into label-report-prefill 2024-07-29 17:01:29 -07:00
Zak Strassberg
f51de34355 fix issues with api usage and import 2024-07-19 06:09:51 +00:00
zak
4e9647651c label generation tool prefills already allocated items 2024-07-18 20:06:44 -07:00
130 changed files with 9883 additions and 6618 deletions

View File

@@ -1,7 +1,7 @@
---
name: "Bug Report"
description: "Submit a bug report for the current release"
labels: ["bug"]
labels: ["🕷️ bug"]
projects: ["sysadminsmedia/2"]
body:
- type: checkboxes
@@ -20,6 +20,8 @@ body:
required: true
- label: I already read the docs and didn't find an answer.
required: true
- label: I can replicate the issue inside the Demo install.
required: true
- type: input
id: homebox-version
attributes:
@@ -55,6 +57,18 @@ body:
- Other
validations:
required: true
- type: dropdown
id: arch
attributes:
label: OS Architechture
description: What type of processor are you running on.
multiple: true
options:
- x86_64 (AMD, Intel)
- ARM64
- ARM/v7
validations:
required: true
- type: textarea
id: os-details
attributes:

View File

@@ -1,7 +1,7 @@
---
name: "Feature Request"
description: "Submit a feature request for the current release"
labels: ["enhancement"]
labels: ["⬆️ enhancement"]
projects: ["sysadminsmedia/2"]
body:
- type: textarea

View File

@@ -2,7 +2,7 @@ name: Docker publish rootless
on:
schedule:
- cron: '00 6 * * *'
- cron: '00 0 * * *'
push:
branches: [ "main" ]
paths:

View File

@@ -2,7 +2,7 @@ name: Docker publish
on:
schedule:
- cron: '00 6 * * *'
- cron: '00 0 * * *'
push:
branches: [ "main" ]
paths:

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>
@@ -24,7 +24,7 @@ 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

View File

@@ -1,7 +1,5 @@
run:
timeout: 10m
skip-dirs:
- internal/data/ent.*
linters-settings:
goconst:
min-len: 5
@@ -45,7 +43,7 @@ linters:
- errcheck
- errorlint
- exhaustive
- exportloopref
- copyloopvar
- gochecknoinits
- goconst
- gocritic
@@ -71,4 +69,6 @@ linters:
- sqlclosecheck
issues:
exclude-use-default: false
exclude-dirs:
- internal/data/ent.*
fix: true

View File

@@ -2,6 +2,7 @@ package main
import (
"context"
"errors"
"strings"
"time"
@@ -9,7 +10,7 @@ import (
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
)
func (a *app) SetupDemo() {
func (a *app) SetupDemo() error {
csvText := `HB.import_ref,HB.location,HB.labels,HB.quantity,HB.name,HB.description,HB.insured,HB.serial_number,HB.model_number,HB.manufacturer,HB.notes,HB.purchase_from,HB.purchase_price,HB.purchase_time,HB.lifetime_warranty,HB.warranty_expires,HB.warranty_details,HB.sold_to,HB.sold_price,HB.sold_time,HB.sold_notes
,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,,,,,,
@@ -33,34 +34,34 @@ func (a *app) SetupDemo() {
_, err := a.services.User.Login(ctx, registration.Email, registration.Password, false)
if err == nil {
log.Info().Msg("Demo user already exists, skipping setup")
return
return nil
}
log.Debug().Msg("Demo user does not exist, setting up demo")
_, err = a.services.User.RegisterUser(ctx, registration)
if err != nil {
log.Err(err).Msg("Failed to register demo user")
log.Fatal().Msg("Failed to setup demo")
return errors.New("failed to setup demo")
}
token, err := a.services.User.Login(ctx, registration.Email, registration.Password, false)
if err != nil {
log.Err(err).Msg("Failed to login demo user")
log.Fatal().Msg("Failed to setup demo")
return
return errors.New("failed to setup demo")
}
self, err := a.services.User.GetSelf(ctx, token.Raw)
if err != nil {
log.Err(err).Msg("Failed to get self")
log.Fatal().Msg("Failed to setup demo")
return
return errors.New("failed to setup demo")
}
_, err = a.services.Items.CsvImport(ctx, self.GroupID, strings.NewReader(csvText))
if err != nil {
log.Err(err).Msg("Failed to import CSV")
log.Fatal().Msg("Failed to setup demo")
return errors.New("failed to setup demo")
}
log.Info().Msg("Demo setup complete")
return nil
}

View File

@@ -85,15 +85,15 @@ func (ctrl *V1Controller) HandleLocationDelete() errchain.HandlerFunc {
return adapters.CommandID("id", fn, http.StatusNoContent)
}
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)
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}})
items, err := ctrl.repo.Items.QueryByGroup(auth, gid, repo.ItemQuery{LocationIDs: []uuid.UUID{id}})
if err != nil {
return repo.LocationOut{}, err
}
@@ -112,7 +112,7 @@ func (ctrl *V1Controller) GetLocationWithPrice(auth context.Context, GID uuid.UU
// Add price from child locations
for _, childLocation := range location.Children {
var childLocationWithPrice repo.LocationOut
childLocationWithPrice, err = ctrl.GetLocationWithPrice(auth, GID, childLocation.ID)
childLocationWithPrice, err = ctrl.GetLocationWithPrice(auth, gid, childLocation.ID)
if err != nil {
return repo.LocationOut{}, err
}

View File

@@ -13,15 +13,16 @@ import (
// HandleMaintenanceLogGet godoc
//
// @Summary Get Maintenance Log
// @Tags Maintenance
// @Tags Item Maintenance
// @Produce json
// @Success 200 {object} repo.MaintenanceLog
// @Param filters query repo.MaintenanceFilters false "which maintenance to retrieve"
// @Success 200 {array} repo.MaintenanceEntryWithDetails[]
// @Router /v1/items/{id}/maintenance [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc {
fn := func(r *http.Request, ID uuid.UUID, q repo.MaintenanceLogQuery) (repo.MaintenanceLog, error) {
fn := func(r *http.Request, ID uuid.UUID, filters repo.MaintenanceFilters) ([]repo.MaintenanceEntryWithDetails, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.MaintEntry.GetLog(auth, auth.GID, ID, q)
return ctrl.repo.MaintEntry.GetMaintenanceByItemID(auth, auth.GID, ID, filters)
}
return adapters.QueryID("id", fn, http.StatusOK)
@@ -30,7 +31,7 @@ func (ctrl *V1Controller) HandleMaintenanceLogGet() errchain.HandlerFunc {
// HandleMaintenanceEntryCreate godoc
//
// @Summary Create Maintenance Entry
// @Tags Maintenance
// @Tags Item Maintenance
// @Produce json
// @Param payload body repo.MaintenanceEntryCreate true "Entry Data"
// @Success 201 {object} repo.MaintenanceEntry
@@ -44,39 +45,3 @@ func (ctrl *V1Controller) HandleMaintenanceEntryCreate() errchain.HandlerFunc {
return adapters.ActionID("id", fn, http.StatusCreated)
}
// HandleMaintenanceEntryDelete godoc
//
// @Summary Delete Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Success 204
// @Router /v1/items/{id}/maintenance/{entry_id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryDelete() errchain.HandlerFunc {
fn := func(r *http.Request, entryID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context())
err := ctrl.repo.MaintEntry.Delete(auth, entryID)
return nil, err
}
return adapters.CommandID("entry_id", fn, http.StatusNoContent)
}
// HandleMaintenanceEntryUpdate godoc
//
// @Summary Update Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Param payload body repo.MaintenanceEntryUpdate true "Entry Data"
// @Success 200 {object} repo.MaintenanceEntry
// @Router /v1/items/{id}/maintenance/{entry_id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, entryID uuid.UUID, body repo.MaintenanceEntryUpdate) (repo.MaintenanceEntry, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.MaintEntry.Update(auth, entryID, body)
}
return adapters.ActionID("entry_id", fn, http.StatusOK)
}

View File

@@ -0,0 +1,65 @@
package v1
import (
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/httpkit/errchain"
"github.com/sysadminsmedia/homebox/backend/internal/core/services"
"github.com/sysadminsmedia/homebox/backend/internal/data/repo"
"github.com/sysadminsmedia/homebox/backend/internal/web/adapters"
)
// HandleMaintenanceGetAll godoc
//
// @Summary Query All Maintenance
// @Tags Maintenance
// @Produce json
// @Param filters query repo.MaintenanceFilters false "which maintenance to retrieve"
// @Success 200 {array} repo.MaintenanceEntryWithDetails[]
// @Router /v1/maintenance [GET]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceGetAll() errchain.HandlerFunc {
fn := func(r *http.Request, filters repo.MaintenanceFilters) ([]repo.MaintenanceEntryWithDetails, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.MaintEntry.GetAllMaintenance(auth, auth.GID, filters)
}
return adapters.Query(fn, http.StatusOK)
}
// HandleMaintenanceEntryUpdate godoc
//
// @Summary Update Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Param payload body repo.MaintenanceEntryUpdate true "Entry Data"
// @Success 200 {object} repo.MaintenanceEntry
// @Router /v1/maintenance/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryUpdate() errchain.HandlerFunc {
fn := func(r *http.Request, entryID uuid.UUID, body repo.MaintenanceEntryUpdate) (repo.MaintenanceEntry, error) {
auth := services.NewContext(r.Context())
return ctrl.repo.MaintEntry.Update(auth, entryID, body)
}
return adapters.ActionID("id", fn, http.StatusOK)
}
// HandleMaintenanceEntryDelete godoc
//
// @Summary Delete Maintenance Entry
// @Tags Maintenance
// @Produce json
// @Success 204
// @Router /v1/maintenance/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleMaintenanceEntryDelete() errchain.HandlerFunc {
fn := func(r *http.Request, entryID uuid.UUID) (any, error) {
auth := services.NewContext(r.Context())
err := ctrl.repo.MaintEntry.Delete(auth, entryID)
return nil, err
}
return adapters.CommandID("id", fn, http.StatusNoContent)
}

View File

@@ -115,16 +115,17 @@ func run(cfg *config.Config) error {
err = c.Schema.Create(context.Background(), options...)
if err != nil {
log.Fatal().
log.Error().
Err(err).
Str("driver", "sqlite").
Str("url", cfg.Storage.SqliteURL).
Msg("failed creating schema resources")
return err
}
err = os.RemoveAll(temp)
if err != nil {
log.Fatal().Err(err).Msg("failed to remove temporary directory for database migrations")
log.Error().Err(err).Msg("failed to remove temporary directory for database migrations")
return err
}
@@ -139,10 +140,11 @@ func run(cfg *config.Config) error {
content, err := os.ReadFile(cfg.Options.CurrencyConfig)
if err != nil {
log.Fatal().
log.Error().
Err(err).
Str("path", cfg.Options.CurrencyConfig).
Msg("failed to read currency config file")
return err
}
collectFuncs = append(collectFuncs, currencies.CollectJSON(bytes.NewReader(content)))
@@ -150,9 +152,10 @@ func run(cfg *config.Config) error {
currencies, err := currencies.CollectionCurrencies(collectFuncs...)
if err != nil {
log.Fatal().
log.Error().
Err(err).
Msg("failed to collect currencies")
return err
}
app.bus = eventbus.New()
@@ -211,7 +214,10 @@ func run(cfg *config.Config) error {
// TODO: Remove through external API that does setup
if cfg.Demo {
log.Info().Msg("Running in demo mode, creating demo data")
app.SetupDemo()
err := app.SetupDemo()
if err != nil {
log.Fatal().Msg(err.Error())
}
}
return nil
})

View File

@@ -4,6 +4,12 @@ import (
"embed"
"errors"
"fmt"
"io"
"mime"
"net/http"
"path"
"path/filepath"
"github.com/go-chi/chi/v5"
"github.com/hay-kot/httpkit/errchain"
httpSwagger "github.com/swaggo/http-swagger/v2" // http-swagger middleware
@@ -13,11 +19,6 @@ 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"
@@ -133,11 +134,14 @@ func (a *app) mountRoutes(r *chi.Mux, chain *errchain.ErrChain, repos *repo.AllR
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("/assets/{id}", chain.ToHandlerFunc(v1Ctrl.HandleAssetGet(), userMW...))
// Maintenance
r.Get("/maintenance", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceGetAll(), userMW...))
r.Put("/maintenance/{id}", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryUpdate(), userMW...))
r.Delete("/maintenance/{id}", chain.ToHandlerFunc(v1Ctrl.HandleMaintenanceEntryDelete(), userMW...))
// Notifiers
r.Get("/notifiers", chain.ToHandlerFunc(v1Ctrl.HandleGetUserNotifiers(), userMW...))
r.Post("/notifiers", chain.ToHandlerFunc(v1Ctrl.HandleCreateNotifier(), userMW...))

View File

@@ -917,14 +917,34 @@ const docTemplate = `{
"application/json"
],
"tags": [
"Maintenance"
"Item Maintenance"
],
"summary": "Get Maintenance Log",
"parameters": [
{
"enum": [
"scheduled",
"completed",
"both"
],
"type": "string",
"x-enum-varnames": [
"MaintenanceFilterStatusScheduled",
"MaintenanceFilterStatusCompleted",
"MaintenanceFilterStatusBoth"
],
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.MaintenanceLog"
"type": "array",
"items": {
"$ref": "#/definitions/repo.MaintenanceEntryWithDetails"
}
}
}
}
@@ -939,7 +959,7 @@ const docTemplate = `{
"application/json"
],
"tags": [
"Maintenance"
"Item Maintenance"
],
"summary": "Create Maintenance Entry",
"parameters": [
@@ -963,60 +983,6 @@ const docTemplate = `{
}
}
},
"/v1/items/{id}/maintenance/{entry_id}": {
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Update Maintenance Entry",
"parameters": [
{
"description": "Entry Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.MaintenanceEntryUpdate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.MaintenanceEntry"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Delete Maintenance Entry",
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/v1/items/{id}/path": {
"get": {
"security": [
@@ -1409,6 +1375,104 @@ const docTemplate = `{
}
}
},
"/v1/maintenance": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Query All Maintenance",
"parameters": [
{
"enum": [
"scheduled",
"completed",
"both"
],
"type": "string",
"x-enum-varnames": [
"MaintenanceFilterStatusScheduled",
"MaintenanceFilterStatusCompleted",
"MaintenanceFilterStatusBoth"
],
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.MaintenanceEntryWithDetails"
}
}
}
}
}
},
"/v1/maintenance/{id}": {
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Update Maintenance Entry",
"parameters": [
{
"description": "Entry Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.MaintenanceEntryUpdate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.MaintenanceEntry"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Delete Maintenance Entry",
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/v1/notifiers": {
"get": {
"security": [
@@ -2476,6 +2540,9 @@ const docTemplate = `{
"parent": {
"$ref": "#/definitions/repo.LocationSummary"
},
"totalPrice": {
"type": "number"
},
"updatedAt": {
"type": "string"
}
@@ -2611,26 +2678,49 @@ const docTemplate = `{
}
}
},
"repo.MaintenanceLog": {
"repo.MaintenanceEntryWithDetails": {
"type": "object",
"properties": {
"costAverage": {
"type": "number"
"completedDate": {
"type": "string"
},
"costTotal": {
"type": "number"
"cost": {
"type": "string",
"example": "0"
},
"entries": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.MaintenanceEntry"
}
"description": {
"type": "string"
},
"itemId": {
"id": {
"type": "string"
},
"itemID": {
"type": "string"
},
"itemName": {
"type": "string"
},
"name": {
"type": "string"
},
"scheduledDate": {
"type": "string"
}
}
},
"repo.MaintenanceFilterStatus": {
"type": "string",
"enum": [
"scheduled",
"completed",
"both"
],
"x-enum-varnames": [
"MaintenanceFilterStatusScheduled",
"MaintenanceFilterStatusCompleted",
"MaintenanceFilterStatusBoth"
]
},
"repo.NotifierCreate": {
"type": "object",
"required": [
@@ -2672,6 +2762,10 @@ const docTemplate = `{
"updatedAt": {
"type": "string"
},
"url": {
"description": "URL field is not exposed to the client",
"type": "string"
},
"userId": {
"type": "string"
}

View File

@@ -910,14 +910,34 @@
"application/json"
],
"tags": [
"Maintenance"
"Item Maintenance"
],
"summary": "Get Maintenance Log",
"parameters": [
{
"enum": [
"scheduled",
"completed",
"both"
],
"type": "string",
"x-enum-varnames": [
"MaintenanceFilterStatusScheduled",
"MaintenanceFilterStatusCompleted",
"MaintenanceFilterStatusBoth"
],
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.MaintenanceLog"
"type": "array",
"items": {
"$ref": "#/definitions/repo.MaintenanceEntryWithDetails"
}
}
}
}
@@ -932,7 +952,7 @@
"application/json"
],
"tags": [
"Maintenance"
"Item Maintenance"
],
"summary": "Create Maintenance Entry",
"parameters": [
@@ -956,60 +976,6 @@
}
}
},
"/v1/items/{id}/maintenance/{entry_id}": {
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Update Maintenance Entry",
"parameters": [
{
"description": "Entry Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.MaintenanceEntryUpdate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.MaintenanceEntry"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Delete Maintenance Entry",
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/v1/items/{id}/path": {
"get": {
"security": [
@@ -1402,6 +1368,104 @@
}
}
},
"/v1/maintenance": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Query All Maintenance",
"parameters": [
{
"enum": [
"scheduled",
"completed",
"both"
],
"type": "string",
"x-enum-varnames": [
"MaintenanceFilterStatusScheduled",
"MaintenanceFilterStatusCompleted",
"MaintenanceFilterStatusBoth"
],
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.MaintenanceEntryWithDetails"
}
}
}
}
}
},
"/v1/maintenance/{id}": {
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Update Maintenance Entry",
"parameters": [
{
"description": "Entry Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.MaintenanceEntryUpdate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.MaintenanceEntry"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Delete Maintenance Entry",
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/v1/notifiers": {
"get": {
"security": [
@@ -2607,26 +2671,49 @@
}
}
},
"repo.MaintenanceLog": {
"repo.MaintenanceEntryWithDetails": {
"type": "object",
"properties": {
"costAverage": {
"type": "number"
"completedDate": {
"type": "string"
},
"costTotal": {
"type": "number"
"cost": {
"type": "string",
"example": "0"
},
"entries": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.MaintenanceEntry"
}
"description": {
"type": "string"
},
"itemId": {
"id": {
"type": "string"
},
"itemID": {
"type": "string"
},
"itemName": {
"type": "string"
},
"name": {
"type": "string"
},
"scheduledDate": {
"type": "string"
}
}
},
"repo.MaintenanceFilterStatus": {
"type": "string",
"enum": [
"scheduled",
"completed",
"both"
],
"x-enum-varnames": [
"MaintenanceFilterStatusScheduled",
"MaintenanceFilterStatusCompleted",
"MaintenanceFilterStatusBoth"
]
},
"repo.NotifierCreate": {
"type": "object",
"required": [
@@ -2668,6 +2755,10 @@
"updatedAt": {
"type": "string"
},
"url": {
"description": "URL field is not exposed to the client",
"type": "string"
},
"userId": {
"type": "string"
}
@@ -2710,9 +2801,6 @@
},
"total": {
"type": "integer"
},
"totalPrice": {
"type": "number"
}
}
},

View File

@@ -390,6 +390,8 @@ definitions:
type: string
parent:
$ref: '#/definitions/repo.LocationSummary'
totalPrice:
type: number
updatedAt:
type: string
type: object
@@ -479,19 +481,36 @@ definitions:
scheduledDate:
type: string
type: object
repo.MaintenanceLog:
repo.MaintenanceEntryWithDetails:
properties:
costAverage:
type: number
costTotal:
type: number
entries:
items:
$ref: '#/definitions/repo.MaintenanceEntry'
type: array
itemId:
completedDate:
type: string
cost:
example: "0"
type: string
description:
type: string
id:
type: string
itemID:
type: string
itemName:
type: string
name:
type: string
scheduledDate:
type: string
type: object
repo.MaintenanceFilterStatus:
enum:
- scheduled
- completed
- both
type: string
x-enum-varnames:
- MaintenanceFilterStatusScheduled
- MaintenanceFilterStatusCompleted
- MaintenanceFilterStatusBoth
repo.NotifierCreate:
properties:
isActive:
@@ -520,6 +539,9 @@ definitions:
type: string
updatedAt:
type: string
url:
description: URL field is not exposed to the client
type: string
userId:
type: string
type: object
@@ -1217,18 +1239,32 @@ paths:
- Items Attachments
/v1/items/{id}/maintenance:
get:
parameters:
- enum:
- scheduled
- completed
- both
in: query
name: status
type: string
x-enum-varnames:
- MaintenanceFilterStatusScheduled
- MaintenanceFilterStatusCompleted
- MaintenanceFilterStatusBoth
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.MaintenanceLog'
items:
$ref: '#/definitions/repo.MaintenanceEntryWithDetails'
type: array
security:
- Bearer: []
summary: Get Maintenance Log
tags:
- Maintenance
- Item Maintenance
post:
parameters:
- description: Entry Data
@@ -1248,39 +1284,7 @@ paths:
- Bearer: []
summary: Create Maintenance Entry
tags:
- Maintenance
/v1/items/{id}/maintenance/{entry_id}:
delete:
produces:
- application/json
responses:
"204":
description: No Content
security:
- Bearer: []
summary: Delete Maintenance Entry
tags:
- Maintenance
put:
parameters:
- description: Entry Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/repo.MaintenanceEntryUpdate'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.MaintenanceEntry'
security:
- Bearer: []
summary: Update Maintenance Entry
tags:
- Maintenance
- Item Maintenance
/v1/items/{id}/path:
get:
parameters:
@@ -1581,6 +1585,66 @@ paths:
summary: Get Locations Tree
tags:
- Locations
/v1/maintenance:
get:
parameters:
- enum:
- scheduled
- completed
- both
in: query
name: status
type: string
x-enum-varnames:
- MaintenanceFilterStatusScheduled
- MaintenanceFilterStatusCompleted
- MaintenanceFilterStatusBoth
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/repo.MaintenanceEntryWithDetails'
type: array
security:
- Bearer: []
summary: Query All Maintenance
tags:
- Maintenance
/v1/maintenance/{id}:
delete:
produces:
- application/json
responses:
"204":
description: No Content
security:
- Bearer: []
summary: Delete Maintenance Entry
tags:
- Maintenance
put:
parameters:
- description: Entry Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/repo.MaintenanceEntryUpdate'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/repo.MaintenanceEntry'
security:
- Bearer: []
summary: Update Maintenance Entry
tags:
- Maintenance
/v1/notifiers:
get:
produces:

View File

@@ -1,31 +1,29 @@
module github.com/sysadminsmedia/homebox/backend
go 1.22
toolchain go1.22.0
go 1.23.0
require (
ariga.io/atlas v0.19.1
entgo.io/ent v0.12.5
github.com/ardanlabs/conf/v3 v3.1.7
entgo.io/ent v0.14.1
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.1
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.24
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.28.0
modernc.org/sqlite v1.33.1
)
require (
@@ -63,16 +61,16 @@ require (
github.com/yeqown/reedsolomon v1.0.0 // indirect
github.com/zclconf/go-cty v1.14.1 // indirect
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/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/tools v0.24.0 // 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/libc v1.55.3 // 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

@@ -1,7 +1,11 @@
ariga.io/atlas v0.19.1 h1:QzBHkakwzEhmPWOzNhw8Yr/Bbicj6Iq5hwEoNI/Jr9A=
ariga.io/atlas v0.19.1/go.mod h1:VPlcXdd4w2KqKnH54yEZcry79UAhpaWaxEsmn5JRNoE=
ariga.io/atlas v0.28.0 h1:qmn9tUyJypJkIw+X3ECUwDtkMTiFupgstHbjRN4xGH0=
ariga.io/atlas v0.28.0/go.mod h1:LOOp18LCL9r+VifvVlJqgYJwYl271rrXD9/wIyzJ8sw=
entgo.io/ent v0.12.5 h1:KREM5E4CSoej4zeGa88Ou/gfturAnpUv0mzAjch1sj4=
entgo.io/ent v0.12.5/go.mod h1:Y3JVAjtlIk8xVZYSn3t3mf8xlZIn5SAOXZQxD6kKI+Q=
entgo.io/ent v0.14.1 h1:fUERL506Pqr92EPHJqr8EYxbPioflJo6PudkrEA8a/s=
entgo.io/ent v0.14.1/go.mod h1:MH6XLG0KXpkcDQhKiHfANZSzR55TJyPL5IGNpI8wpco=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
@@ -10,8 +14,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 +31,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 +58,25 @@ 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-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/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 +87,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 +116,21 @@ 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-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
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/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/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/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
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,10 +144,14 @@ 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/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -145,41 +160,55 @@ 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=
github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
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/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
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/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.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/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
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/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.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/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.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=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -194,16 +223,20 @@ 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/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
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/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
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=

View File

@@ -49,7 +49,7 @@ func bootstrap() {
}
}
func TestMain(m *testing.M) {
func MainNoExit(m *testing.M) int {
client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
@@ -77,5 +77,9 @@ func TestMain(m *testing.M) {
UID: tUser.ID,
}
os.Exit(m.Run())
return m.Run()
}
func TestMain(m *testing.M) {
os.Exit(MainNoExit(m))
}

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, hbURL string) 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{}{}
@@ -164,7 +164,7 @@ func (s *IOSheet) ReadItems(ctx context.Context, items []repo.ItemOut, GID uuid.
// TODO: Support fetching nested locations
locID := item.Location.ID
locPaths, err := repos.Locations.PathForLoc(context.Background(), GID, locID)
locPaths, err := repos.Locations.PathForLoc(context.Background(), gid, locID)
if err != nil {
log.Error().Err(err).Msg("could not get location path")
return err

View File

@@ -38,13 +38,13 @@ func (svc *ItemService) Create(ctx Context, item repo.ItemCreate) (repo.ItemOut,
return svc.repo.Items.Create(ctx, ctx.GID, item)
}
func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int, error) {
items, err := svc.repo.Items.GetAllZeroAssetID(ctx, GID)
func (svc *ItemService) EnsureAssetID(ctx context.Context, gid uuid.UUID) (int, error) {
items, err := svc.repo.Items.GetAllZeroAssetID(ctx, gid)
if err != nil {
return 0, err
}
highest, err := svc.repo.Items.GetHighestAssetID(ctx, GID)
highest, err := svc.repo.Items.GetHighestAssetID(ctx, gid)
if err != nil {
return 0, err
}
@@ -53,7 +53,7 @@ func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int,
for _, item := range items {
highest++
err = svc.repo.Items.SetAssetID(ctx, GID, item.ID, highest)
err = svc.repo.Items.SetAssetID(ctx, gid, item.ID, highest)
if err != nil {
return 0, err
}
@@ -64,8 +64,8 @@ func (svc *ItemService) EnsureAssetID(ctx context.Context, GID uuid.UUID) (int,
return finished, nil
}
func (svc *ItemService) EnsureImportRef(ctx context.Context, GID uuid.UUID) (int, error) {
ids, err := svc.repo.Items.GetAllZeroImportRef(ctx, GID)
func (svc *ItemService) EnsureImportRef(ctx context.Context, gid uuid.UUID) (int, error) {
ids, err := svc.repo.Items.GetAllZeroImportRef(ctx, gid)
if err != nil {
return 0, err
}
@@ -74,7 +74,7 @@ func (svc *ItemService) EnsureImportRef(ctx context.Context, GID uuid.UUID) (int
for _, itemID := range ids {
ref := uuid.New().String()[0:8]
err = svc.repo.Items.Patch(ctx, GID, itemID, repo.ItemPatch{ImportRef: &ref})
err = svc.repo.Items.Patch(ctx, gid, itemID, repo.ItemPatch{ImportRef: &ref})
if err != nil {
return 0, err
}
@@ -96,7 +96,7 @@ func serializeLocation[T ~[]string](location T) string {
// 1. If the item does not exist, it is created.
// 2. If the item has a ImportRef and it exists it is skipped
// 3. Locations and Labels are created if they do not exist.
func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Reader) (int, error) {
func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data io.Reader) (int, error) {
sheet := reporting.IOSheet{}
err := sheet.Read(data)
@@ -109,7 +109,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
labelMap := make(map[string]uuid.UUID)
{
labels, err := svc.repo.Labels.GetAll(ctx, GID)
labels, err := svc.repo.Labels.GetAll(ctx, gid)
if err != nil {
return 0, err
}
@@ -124,7 +124,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
locationMap := make(map[string]uuid.UUID)
{
locations, err := svc.repo.Locations.Tree(ctx, GID, repo.TreeQuery{WithItems: false})
locations, err := svc.repo.Locations.Tree(ctx, gid, repo.TreeQuery{WithItems: false})
if err != nil {
return 0, err
}
@@ -153,7 +153,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
// Asset ID Pre-Check
highestAID := repo.AssetID(-1)
if svc.autoIncrementAssetID {
highestAID, err = svc.repo.Items.GetHighestAssetID(ctx, GID)
highestAID, err = svc.repo.Items.GetHighestAssetID(ctx, gid)
if err != nil {
return 0, err
}
@@ -169,7 +169,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
// ========================================
// Preflight check for existing item
if row.ImportRef != "" {
exists, err := svc.repo.Items.CheckRef(ctx, GID, row.ImportRef)
exists, err := svc.repo.Items.CheckRef(ctx, gid, row.ImportRef)
if err != nil {
return 0, fmt.Errorf("error checking for existing item with ref %q: %w", row.ImportRef, err)
}
@@ -188,7 +188,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
id, ok := labelMap[label]
if !ok {
newLabel, err := svc.repo.Labels.Create(ctx, GID, repo.LabelCreate{Name: label})
newLabel, err := svc.repo.Labels.Create(ctx, gid, repo.LabelCreate{Name: label})
if err != nil {
return 0, err
}
@@ -220,7 +220,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
parentID = locationMap[parentPath]
}
newLocation, err := svc.repo.Locations.Create(ctx, GID, repo.LocationCreate{
newLocation, err := svc.repo.Locations.Create(ctx, gid, repo.LocationCreate{
ParentID: parentID,
Name: pathElement,
})
@@ -261,12 +261,12 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
LabelIDs: labelIds,
}
item, err = svc.repo.Items.Create(ctx, GID, newItem)
item, err = svc.repo.Items.Create(ctx, gid, newItem)
if err != nil {
return 0, err
}
default:
item, err = svc.repo.Items.GetByRef(ctx, GID, row.ImportRef)
item, err = svc.repo.Items.GetByRef(ctx, gid, row.ImportRef)
if err != nil {
return 0, err
}
@@ -318,7 +318,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, GID uuid.UUID, data io.Re
Fields: fields,
}
item, err = svc.repo.Items.UpdateByGroup(ctx, GID, updateItem)
item, err = svc.repo.Items.UpdateByGroup(ctx, gid, updateItem)
if err != nil {
return 0, err
}
@@ -329,15 +329,15 @@ 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, hbURL string) ([][]string, error) {
items, err := svc.repo.Items.GetAll(ctx, GID)
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
}
sheet := reporting.IOSheet{}
err = sheet.ReadItems(ctx, items, GID, svc.repo, hbURL)
err = sheet.ReadItems(ctx, items, gid, svc.repo, hbURL)
if err != nil {
return nil, err
}
@@ -345,8 +345,8 @@ func (svc *ItemService) ExportCSV(ctx context.Context, GID uuid.UUID, hbURL stri
return sheet.CSV()
}
func (svc *ItemService) ExportBillOfMaterialsCSV(ctx context.Context, GID uuid.UUID) ([]byte, error) {
items, err := svc.repo.Items.GetAll(ctx, GID)
func (svc *ItemService) ExportBillOfMaterialsCSV(ctx context.Context, gid uuid.UUID) ([]byte, error) {
items, err := svc.repo.Items.GetAll(ctx, gid)
if err != nil {
return nil, err
}

View File

@@ -132,13 +132,13 @@ func (svc *UserService) GetSelf(ctx context.Context, requestToken string) (repo.
return svc.repos.AuthTokens.GetUserFromToken(ctx, hash)
}
func (svc *UserService) UpdateSelf(ctx context.Context, ID uuid.UUID, data repo.UserUpdate) (repo.UserOut, error) {
err := svc.repos.Users.Update(ctx, ID, data)
func (svc *UserService) UpdateSelf(ctx context.Context, id uuid.UUID, data repo.UserUpdate) (repo.UserOut, error) {
err := svc.repos.Users.Update(ctx, id, data)
if err != nil {
return repo.UserOut{}, err
}
return svc.repos.Users.GetOneID(ctx, ID)
return svc.repos.Users.GetOneID(ctx, id)
}
// ============================================================================
@@ -217,8 +217,8 @@ func (svc *UserService) RenewToken(ctx context.Context, token string) (UserAuthT
// DeleteSelf deletes the user that is currently logged based of the provided UUID
// There is _NO_ protection against deleting the wrong user, as such this should only
// be used when the identify of the user has been confirmed.
func (svc *UserService) DeleteSelf(ctx context.Context, ID uuid.UUID) error {
return svc.repos.Users.Delete(ctx, ID)
func (svc *UserService) DeleteSelf(ctx context.Context, id uuid.UUID) error {
return svc.repos.Users.Delete(ctx, id)
}
func (svc *UserService) ChangePassword(ctx Context, current string, new string) (ok bool) {

View File

@@ -16,9 +16,9 @@ func (aid AssetID) Int() int {
return int(aid)
}
func ParseAssetIDBytes(d []byte) (AID AssetID, ok bool) {
d = bytes.Replace(d, []byte(`"`), []byte(``), -1)
d = bytes.Replace(d, []byte(`-`), []byte(``), -1)
func ParseAssetIDBytes(d []byte) (aid AssetID, ok bool) {
d = bytes.ReplaceAll(d, []byte(`"`), []byte(``))
d = bytes.ReplaceAll(d, []byte(`-`), []byte(``))
aidInt, err := strconv.Atoi(string(d))
if err != nil {
@@ -28,7 +28,7 @@ func ParseAssetIDBytes(d []byte) (AID AssetID, ok bool) {
return AssetID(aidInt), true
}
func ParseAssetID(s string) (AID AssetID, ok bool) {
func ParseAssetID(s string) (aid AssetID, ok bool) {
return ParseAssetIDBytes([]byte(s))
}
@@ -52,8 +52,8 @@ func (aid *AssetID) UnmarshalJSON(d []byte) error {
return nil
}
d = bytes.Replace(d, []byte(`"`), []byte(``), -1)
d = bytes.Replace(d, []byte(`-`), []byte(``), -1)
d = bytes.ReplaceAll(d, []byte(`"`), []byte(``))
d = bytes.ReplaceAll(d, []byte(`-`), []byte(``))
aidInt, err := strconv.Atoi(string(d))
if err != nil {

View File

@@ -39,7 +39,7 @@ func bootstrap() {
}
}
func TestMain(m *testing.M) {
func MainNoExit(m *testing.M) int {
client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
@@ -59,6 +59,9 @@ func TestMain(m *testing.M) {
defer func() { _ = client.Close() }()
bootstrap()
os.Exit(m.Run())
return m.Run()
}
func TestMain(m *testing.M) {
os.Exit(MainNoExit(m))
}

View File

@@ -109,12 +109,12 @@ func (r *GroupRepository) GetAllGroups(ctx context.Context) ([]Group, error) {
return r.groupMapper.MapEachErr(r.db.Group.Query().All(ctx))
}
func (r *GroupRepository) StatsLocationsByPurchasePrice(ctx context.Context, GID uuid.UUID) ([]TotalsByOrganizer, error) {
func (r *GroupRepository) StatsLocationsByPurchasePrice(ctx context.Context, gid uuid.UUID) ([]TotalsByOrganizer, error) {
var v []TotalsByOrganizer
err := r.db.Location.Query().
Where(
location.HasGroupWith(group.ID(GID)),
location.HasGroupWith(group.ID(gid)),
).
GroupBy(location.FieldID, location.FieldName).
Aggregate(func(sq *sql.Selector) string {
@@ -131,12 +131,12 @@ func (r *GroupRepository) StatsLocationsByPurchasePrice(ctx context.Context, GID
return v, err
}
func (r *GroupRepository) StatsLabelsByPurchasePrice(ctx context.Context, GID uuid.UUID) ([]TotalsByOrganizer, error) {
func (r *GroupRepository) StatsLabelsByPurchasePrice(ctx context.Context, gid uuid.UUID) ([]TotalsByOrganizer, error) {
var v []TotalsByOrganizer
err := r.db.Label.Query().
Where(
label.HasGroupWith(group.ID(GID)),
label.HasGroupWith(group.ID(gid)),
).
GroupBy(label.FieldID, label.FieldName).
Aggregate(func(sq *sql.Selector) string {
@@ -157,7 +157,7 @@ func (r *GroupRepository) StatsLabelsByPurchasePrice(ctx context.Context, GID uu
return v, err
}
func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID, start, end time.Time) (*ValueOverTime, error) {
func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, gid uuid.UUID, start, end time.Time) (*ValueOverTime, error) {
// Get the Totals for the Start and End of the Given Time Period
q := `
SELECT
@@ -180,7 +180,7 @@ func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID,
var maybeStart *float64
var maybeEnd *float64
row := r.db.Sql().QueryRowContext(ctx, q, GID, sqliteDateFormat(start), GID, sqliteDateFormat(end))
row := r.db.Sql().QueryRowContext(ctx, q, gid, sqliteDateFormat(start), gid, sqliteDateFormat(end))
err := row.Scan(&maybeStart, &maybeEnd)
if err != nil {
return nil, err
@@ -198,7 +198,7 @@ func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID,
// Get Created Date and Price of all items between start and end
err = r.db.Item.Query().
Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(gid)),
item.CreatedAtGTE(start),
item.CreatedAtLTE(end),
item.Archived(false),
@@ -226,7 +226,7 @@ func (r *GroupRepository) StatsPurchasePrice(ctx context.Context, GID uuid.UUID,
return &stats, nil
}
func (r *GroupRepository) StatsGroup(ctx context.Context, GID uuid.UUID) (GroupStatistics, error) {
func (r *GroupRepository) StatsGroup(ctx context.Context, gid uuid.UUID) (GroupStatistics, error) {
q := `
SELECT
(SELECT COUNT(*) FROM users WHERE group_users = ?) AS total_users,
@@ -242,7 +242,7 @@ func (r *GroupRepository) StatsGroup(ctx context.Context, GID uuid.UUID) (GroupS
) AS total_with_warranty
`
var stats GroupStatistics
row := r.db.Sql().QueryRowContext(ctx, q, GID, GID, GID, GID, GID, GID)
row := r.db.Sql().QueryRowContext(ctx, q, gid, gid, gid, gid, gid, gid)
var maybeTotalItemPrice *float64
var maybeTotalWithWarranty *int
@@ -264,8 +264,8 @@ func (r *GroupRepository) GroupCreate(ctx context.Context, name string) (Group,
Save(ctx))
}
func (r *GroupRepository) GroupUpdate(ctx context.Context, ID uuid.UUID, data GroupUpdate) (Group, error) {
entity, err := r.db.Group.UpdateOneID(ID).
func (r *GroupRepository) GroupUpdate(ctx context.Context, id uuid.UUID, data GroupUpdate) (Group, error) {
entity, err := r.db.Group.UpdateOneID(id).
SetName(data.Name).
SetCurrency(strings.ToLower(data.Currency)).
Save(ctx)

View File

@@ -70,8 +70,8 @@ type (
ParentID uuid.UUID `json:"parentId" extensions:"x-nullable,x-omitempty"`
ID uuid.UUID `json:"id"`
AssetID AssetID `json:"assetId" swaggertype:"string"`
Name string `json:"name"`
Description string `json:"description"`
Name string `json:"name" validate:"required,min=1,max=255"`
Description string `json:"description" validate:"max=1000"`
Quantity int `json:"quantity"`
Insured bool `json:"insured"`
Archived bool `json:"archived"`
@@ -92,12 +92,12 @@ type (
// Purchase
PurchaseTime types.Date `json:"purchaseTime"`
PurchaseFrom string `json:"purchaseFrom"`
PurchaseFrom string `json:"purchaseFrom" validate:"max=255"`
PurchasePrice float64 `json:"purchasePrice,string"`
// Sold
SoldTime types.Date `json:"soldTime"`
SoldTo string `json:"soldTo"`
SoldTo string `json:"soldTo" validate:"max=255"`
SoldPrice float64 `json:"soldPrice,string"`
SoldNotes string `json:"soldNotes"`
@@ -277,9 +277,9 @@ func mapItemOut(item *ent.Item) ItemOut {
}
}
func (e *ItemsRepository) publishMutationEvent(GID uuid.UUID) {
func (e *ItemsRepository) publishMutationEvent(gid uuid.UUID) {
if e.bus != nil {
e.bus.Publish(eventbus.EventItemMutation, eventbus.GroupMutationEvent{GID: GID})
e.bus.Publish(eventbus.EventItemMutation, eventbus.GroupMutationEvent{GID: gid})
}
}
@@ -305,13 +305,13 @@ func (e *ItemsRepository) GetOne(ctx context.Context, id uuid.UUID) (ItemOut, er
return e.getOne(ctx, item.ID(id))
}
func (e *ItemsRepository) CheckRef(ctx context.Context, GID uuid.UUID, ref string) (bool, error) {
q := e.db.Item.Query().Where(item.HasGroupWith(group.ID(GID)))
func (e *ItemsRepository) CheckRef(ctx context.Context, gid uuid.UUID, ref string) (bool, error) {
q := e.db.Item.Query().Where(item.HasGroupWith(group.ID(gid)))
return q.Where(item.ImportRef(ref)).Exist(ctx)
}
func (e *ItemsRepository) GetByRef(ctx context.Context, GID uuid.UUID, ref string) (ItemOut, error) {
return e.getOne(ctx, item.ImportRef(ref), item.HasGroupWith(group.ID(GID)))
func (e *ItemsRepository) GetByRef(ctx context.Context, gid uuid.UUID, ref string) (ItemOut, error) {
return e.getOne(ctx, item.ImportRef(ref), item.HasGroupWith(group.ID(gid)))
}
// GetOneByGroup returns a single item by ID. If the item does not exist, an error is returned.
@@ -498,9 +498,9 @@ func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]ItemOut,
All(ctx))
}
func (e *ItemsRepository) GetAllZeroAssetID(ctx context.Context, GID uuid.UUID) ([]ItemSummary, error) {
func (e *ItemsRepository) GetAllZeroAssetID(ctx context.Context, gid uuid.UUID) ([]ItemSummary, error) {
q := e.db.Item.Query().Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(gid)),
item.AssetID(0),
).Order(
ent.Asc(item.FieldCreatedAt),
@@ -509,9 +509,9 @@ func (e *ItemsRepository) GetAllZeroAssetID(ctx context.Context, GID uuid.UUID)
return mapItemsSummaryErr(q.All(ctx))
}
func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, GID uuid.UUID) (AssetID, error) {
func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, gid uuid.UUID) (AssetID, error) {
q := e.db.Item.Query().Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(gid)),
).Order(
ent.Desc(item.FieldAssetID),
).Limit(1)
@@ -527,10 +527,10 @@ func (e *ItemsRepository) GetHighestAssetID(ctx context.Context, GID uuid.UUID)
return AssetID(result.AssetID), nil
}
func (e *ItemsRepository) SetAssetID(ctx context.Context, GID uuid.UUID, ID uuid.UUID, assetID AssetID) error {
func (e *ItemsRepository) SetAssetID(ctx context.Context, gid uuid.UUID, id uuid.UUID, assetID AssetID) error {
q := e.db.Item.Update().Where(
item.HasGroupWith(group.ID(GID)),
item.ID(ID),
item.HasGroupWith(group.ID(gid)),
item.ID(id),
)
_, err := q.SetAssetID(int(assetID)).Save(ctx)
@@ -546,7 +546,7 @@ func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data ItemCr
SetLocationID(data.LocationID).
SetAssetID(int(data.AssetID))
if data.LabelIDs != nil && len(data.LabelIDs) > 0 {
if len(data.LabelIDs) > 0 {
q.AddLabelIDs(data.LabelIDs...)
}
@@ -584,8 +584,8 @@ func (e *ItemsRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID)
return err
}
func (e *ItemsRepository) UpdateByGroup(ctx context.Context, GID uuid.UUID, data ItemUpdate) (ItemOut, error) {
q := e.db.Item.Update().Where(item.ID(data.ID), item.HasGroupWith(group.ID(GID))).
func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data ItemUpdate) (ItemOut, error) {
q := e.db.Item.Update().Where(item.ID(data.ID), item.HasGroupWith(group.ID(gid))).
SetName(data.Name).
SetDescription(data.Description).
SetLocationID(data.LocationID).
@@ -696,16 +696,16 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, GID uuid.UUID, data
}
}
e.publishMutationEvent(GID)
e.publishMutationEvent(gid)
return e.GetOne(ctx, data.ID)
}
func (e *ItemsRepository) GetAllZeroImportRef(ctx context.Context, GID uuid.UUID) ([]uuid.UUID, error) {
func (e *ItemsRepository) GetAllZeroImportRef(ctx context.Context, gid uuid.UUID) ([]uuid.UUID, error) {
var ids []uuid.UUID
err := e.db.Item.Query().
Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(gid)),
item.Or(
item.ImportRefEQ(""),
item.ImportRefIsNil(),
@@ -720,11 +720,11 @@ func (e *ItemsRepository) GetAllZeroImportRef(ctx context.Context, GID uuid.UUID
return ids, nil
}
func (e *ItemsRepository) Patch(ctx context.Context, GID, ID uuid.UUID, data ItemPatch) error {
func (e *ItemsRepository) Patch(ctx context.Context, gid, id uuid.UUID, data ItemPatch) error {
q := e.db.Item.Update().
Where(
item.ID(ID),
item.HasGroupWith(group.ID(GID)),
item.ID(id),
item.HasGroupWith(group.ID(gid)),
)
if data.ImportRef != nil {
@@ -735,11 +735,11 @@ func (e *ItemsRepository) Patch(ctx context.Context, GID, ID uuid.UUID, data Ite
q.SetQuantity(*data.Quantity)
}
e.publishMutationEvent(GID)
e.publishMutationEvent(gid)
return q.Exec(ctx)
}
func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, GID uuid.UUID, name string) ([]string, error) {
func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, gid uuid.UUID, name string) ([]string, error) {
type st struct {
Value string `json:"text_value"`
}
@@ -748,7 +748,7 @@ func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, GID uuid.
err := e.db.Item.Query().
Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(gid)),
).
QueryFields().
Where(
@@ -769,7 +769,7 @@ func (e *ItemsRepository) GetAllCustomFieldValues(ctx context.Context, GID uuid.
return valueStrings, nil
}
func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, GID uuid.UUID) ([]string, error) {
func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, gid uuid.UUID) ([]string, error) {
type st struct {
Name string `json:"name"`
}
@@ -778,7 +778,7 @@ func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, GID uuid.U
err := e.db.Item.Query().
Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(gid)),
).
QueryFields().
Unique(true).
@@ -802,9 +802,9 @@ func (e *ItemsRepository) GetAllCustomFieldNames(ctx context.Context, GID uuid.U
// This is designed to resolve a long-time bug that has since been fixed with the time selector on the
// frontend. This function is intended to be used as a one-time fix for existing databases and may be
// removed in the future.
func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, GID uuid.UUID) (int, error) {
func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, gid uuid.UUID) (int, error) {
q := e.db.Item.Query().Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(gid)),
item.Or(
item.PurchaseTimeNotNil(),
item.PurchaseFromLT("0002-01-01"),
@@ -873,11 +873,11 @@ func (e *ItemsRepository) ZeroOutTimeFields(ctx context.Context, GID uuid.UUID)
return updated, nil
}
func (e *ItemsRepository) SetPrimaryPhotos(ctx context.Context, GID uuid.UUID) (int, error) {
func (e *ItemsRepository) SetPrimaryPhotos(ctx context.Context, gid uuid.UUID) (int, error) {
// All items where there is no primary photo
itemIDs, err := e.db.Item.Query().
Where(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(gid)),
item.HasAttachmentsWith(
attachment.TypeEQ(attachment.TypePhoto),
attachment.Not(

View File

@@ -65,9 +65,9 @@ func mapLabelOut(label *ent.Label) LabelOut {
}
}
func (r *LabelRepository) publishMutationEvent(GID uuid.UUID) {
func (r *LabelRepository) publishMutationEvent(gid uuid.UUID) {
if r.bus != nil {
r.bus.Publish(eventbus.EventLabelMutation, eventbus.GroupMutationEvent{GID: GID})
r.bus.Publish(eventbus.EventLabelMutation, eventbus.GroupMutationEvent{GID: gid})
}
}
@@ -79,8 +79,8 @@ func (r *LabelRepository) getOne(ctx context.Context, where ...predicate.Label)
)
}
func (r *LabelRepository) GetOne(ctx context.Context, ID uuid.UUID) (LabelOut, error) {
return r.getOne(ctx, label.ID(ID))
func (r *LabelRepository) GetOne(ctx context.Context, id uuid.UUID) (LabelOut, error) {
return r.getOne(ctx, label.ID(id))
}
func (r *LabelRepository) GetOneByGroup(ctx context.Context, gid, ld uuid.UUID) (LabelOut, error) {
@@ -125,13 +125,13 @@ func (r *LabelRepository) update(ctx context.Context, data LabelUpdate, where ..
Save(ctx)
}
func (r *LabelRepository) UpdateByGroup(ctx context.Context, GID uuid.UUID, data LabelUpdate) (LabelOut, error) {
_, err := r.update(ctx, data, label.ID(data.ID), label.HasGroupWith(group.ID(GID)))
func (r *LabelRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data LabelUpdate) (LabelOut, error) {
_, err := r.update(ctx, data, label.ID(data.ID), label.HasGroupWith(group.ID(gid)))
if err != nil {
return LabelOut{}, err
}
r.publishMutationEvent(GID)
r.publishMutationEvent(gid)
return r.GetOne(ctx, data.ID)
}

View File

@@ -90,9 +90,9 @@ func mapLocationOut(location *ent.Location) LocationOut {
}
}
func (r *LocationRepository) publishMutationEvent(GID uuid.UUID) {
func (r *LocationRepository) publishMutationEvent(gid uuid.UUID) {
if r.bus != nil {
r.bus.Publish(eventbus.EventLocationMutation, eventbus.GroupMutationEvent{GID: GID})
r.bus.Publish(eventbus.EventLocationMutation, eventbus.GroupMutationEvent{GID: gid})
}
}
@@ -101,7 +101,7 @@ type LocationQuery struct {
}
// GetAll returns all locations with item count field populated
func (r *LocationRepository) GetAll(ctx context.Context, GID uuid.UUID, filter LocationQuery) ([]LocationOutCount, error) {
func (r *LocationRepository) GetAll(ctx context.Context, gid uuid.UUID, filter LocationQuery) ([]LocationOutCount, error) {
query := `--sql
SELECT
id,
@@ -132,7 +132,7 @@ func (r *LocationRepository) GetAll(ctx context.Context, GID uuid.UUID, filter L
query = strings.Replace(query, "{{ FILTER_CHILDREN }}", "", 1)
}
rows, err := r.db.Sql().QueryContext(ctx, query, GID)
rows, err := r.db.Sql().QueryContext(ctx, query, gid)
if err != nil {
return nil, err
}
@@ -168,19 +168,19 @@ func (r *LocationRepository) getOne(ctx context.Context, where ...predicate.Loca
Only(ctx))
}
func (r *LocationRepository) Get(ctx context.Context, ID uuid.UUID) (LocationOut, error) {
return r.getOne(ctx, location.ID(ID))
func (r *LocationRepository) Get(ctx context.Context, id uuid.UUID) (LocationOut, error) {
return r.getOne(ctx, location.ID(id))
}
func (r *LocationRepository) GetOneByGroup(ctx context.Context, GID, ID uuid.UUID) (LocationOut, error) {
return r.getOne(ctx, location.ID(ID), location.HasGroupWith(group.ID(GID)))
func (r *LocationRepository) GetOneByGroup(ctx context.Context, gid, id uuid.UUID) (LocationOut, error) {
return r.getOne(ctx, location.ID(id), location.HasGroupWith(group.ID(gid)))
}
func (r *LocationRepository) Create(ctx context.Context, GID uuid.UUID, data LocationCreate) (LocationOut, error) {
func (r *LocationRepository) Create(ctx context.Context, gid uuid.UUID, data LocationCreate) (LocationOut, error) {
q := r.db.Location.Create().
SetName(data.Name).
SetDescription(data.Description).
SetGroupID(GID)
SetGroupID(gid)
if data.ParentID != uuid.Nil {
q.SetParentID(data.ParentID)
@@ -191,8 +191,8 @@ func (r *LocationRepository) Create(ctx context.Context, GID uuid.UUID, data Loc
return LocationOut{}, err
}
location.Edges.Group = &ent.Group{ID: GID} // bootstrap group ID
r.publishMutationEvent(GID)
location.Edges.Group = &ent.Group{ID: gid} // bootstrap group ID
r.publishMutationEvent(gid)
return mapLocationOut(location), nil
}
@@ -216,28 +216,28 @@ func (r *LocationRepository) update(ctx context.Context, data LocationUpdate, wh
return r.Get(ctx, data.ID)
}
func (r *LocationRepository) UpdateByGroup(ctx context.Context, GID, ID uuid.UUID, data LocationUpdate) (LocationOut, error) {
v, err := r.update(ctx, data, location.ID(ID), location.HasGroupWith(group.ID(GID)))
func (r *LocationRepository) UpdateByGroup(ctx context.Context, gid, id uuid.UUID, data LocationUpdate) (LocationOut, error) {
v, err := r.update(ctx, data, location.ID(id), location.HasGroupWith(group.ID(gid)))
if err != nil {
return LocationOut{}, err
}
r.publishMutationEvent(GID)
r.publishMutationEvent(gid)
return v, err
}
// delete should only be used after checking that the location is owned by the
// group. Otherwise, use DeleteByGroup
func (r *LocationRepository) delete(ctx context.Context, ID uuid.UUID) error {
return r.db.Location.DeleteOneID(ID).Exec(ctx)
func (r *LocationRepository) delete(ctx context.Context, id uuid.UUID) error {
return r.db.Location.DeleteOneID(id).Exec(ctx)
}
func (r *LocationRepository) DeleteByGroup(ctx context.Context, GID, ID uuid.UUID) error {
_, err := r.db.Location.Delete().Where(location.ID(ID), location.HasGroupWith(group.ID(GID))).Exec(ctx)
func (r *LocationRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID) error {
_, err := r.db.Location.Delete().Where(location.ID(id), location.HasGroupWith(group.ID(gid))).Exec(ctx)
if err != nil {
return err
}
r.publishMutationEvent(GID)
r.publishMutationEvent(gid)
return err
}
@@ -274,7 +274,7 @@ type ItemPath struct {
Name string `json:"name"`
}
func (r *LocationRepository) PathForLoc(ctx context.Context, GID, locID uuid.UUID) ([]ItemPath, error) {
func (r *LocationRepository) PathForLoc(ctx context.Context, gid, locID uuid.UUID) ([]ItemPath, error) {
query := `WITH RECURSIVE location_path AS (
SELECT id, name, location_children
FROM locations
@@ -291,7 +291,7 @@ func (r *LocationRepository) PathForLoc(ctx context.Context, GID, locID uuid.UUI
SELECT id, name
FROM location_path`
rows, err := r.db.Sql().QueryContext(ctx, query, locID, GID)
rows, err := r.db.Sql().QueryContext(ctx, query, locID, gid)
if err != nil {
return nil, err
}
@@ -321,7 +321,7 @@ func (r *LocationRepository) PathForLoc(ctx context.Context, GID, locID uuid.UUI
return locations, nil
}
func (r *LocationRepository) Tree(ctx context.Context, GID uuid.UUID, tq TreeQuery) ([]TreeItem, error) {
func (r *LocationRepository) Tree(ctx context.Context, gid uuid.UUID, tq TreeQuery) ([]TreeItem, error) {
query := `
WITH recursive location_tree(id, NAME, parent_id, level, node_type) AS
(
@@ -403,7 +403,7 @@ func (r *LocationRepository) Tree(ctx context.Context, GID uuid.UUID, tq TreeQue
query = strings.ReplaceAll(query, "{{ WITH_ITEMS_FROM }}", "")
}
rows, err := r.db.Sql().QueryContext(ctx, query, GID)
rows, err := r.db.Sql().QueryContext(ctx, query, gid)
if err != nil {
return nil, err
}

View File

@@ -0,0 +1,72 @@
package repo
import (
"context"
"time"
"github.com/google/uuid"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/group"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/item"
"github.com/sysadminsmedia/homebox/backend/internal/data/ent/maintenanceentry"
)
type (
MaintenanceEntryWithDetails struct {
MaintenanceEntry
ItemName string `json:"itemName"`
ItemID uuid.UUID `json:"itemID"`
}
)
var (
mapEachMaintenanceEntryWithDetails = mapTEachFunc(mapMaintenanceEntryWithDetails)
)
func mapMaintenanceEntryWithDetails(entry *ent.MaintenanceEntry) MaintenanceEntryWithDetails {
return MaintenanceEntryWithDetails{
MaintenanceEntry: mapMaintenanceEntry(entry),
ItemName: entry.Edges.Item.Name,
ItemID: entry.ItemID,
}
}
type MaintenanceFilterStatus string
const (
MaintenanceFilterStatusScheduled MaintenanceFilterStatus = "scheduled"
MaintenanceFilterStatusCompleted MaintenanceFilterStatus = "completed"
MaintenanceFilterStatusBoth MaintenanceFilterStatus = "both"
)
type MaintenanceFilters struct {
Status MaintenanceFilterStatus `json:"status" schema:"status"`
}
func (r *MaintenanceEntryRepository) GetAllMaintenance(ctx context.Context, groupID uuid.UUID, filters MaintenanceFilters) ([]MaintenanceEntryWithDetails, error) {
query := r.db.MaintenanceEntry.Query().Where(
maintenanceentry.HasItemWith(
item.HasGroupWith(group.IDEQ(groupID)),
),
)
if filters.Status == MaintenanceFilterStatusScheduled {
query = query.Where(maintenanceentry.Or(
maintenanceentry.DateIsNil(),
maintenanceentry.DateEQ(time.Time{}),
))
} else if filters.Status == MaintenanceFilterStatusCompleted {
query = query.Where(
maintenanceentry.Not(maintenanceentry.Or(
maintenanceentry.DateIsNil(),
maintenanceentry.DateEQ(time.Time{})),
))
}
entries, err := query.WithItem().Order(maintenanceentry.ByScheduledDate()).All(ctx)
if err != nil {
return nil, err
}
return mapEachMaintenanceEntryWithDetails(entries), nil
}

View File

@@ -59,13 +59,6 @@ type (
Description string `json:"description"`
Cost float64 `json:"cost,string"`
}
MaintenanceLog struct {
ItemID uuid.UUID `json:"itemId"`
CostAverage float64 `json:"costAverage"`
CostTotal float64 `json:"costTotal"`
Entries []MaintenanceEntry `json:"entries"`
}
)
var (
@@ -84,11 +77,11 @@ func mapMaintenanceEntry(entry *ent.MaintenanceEntry) MaintenanceEntry {
}
}
func (r *MaintenanceEntryRepository) GetScheduled(ctx context.Context, GID uuid.UUID, dt types.Date) ([]MaintenanceEntry, error) {
func (r *MaintenanceEntryRepository) GetScheduled(ctx context.Context, gid uuid.UUID, dt types.Date) ([]MaintenanceEntry, error) {
entries, err := r.db.MaintenanceEntry.Query().
Where(
maintenanceentry.HasItemWith(
item.HasGroupWith(group.ID(GID)),
item.HasGroupWith(group.ID(gid)),
),
maintenanceentry.ScheduledDate(dt.Time()),
maintenanceentry.Or(
@@ -118,8 +111,8 @@ func (r *MaintenanceEntryRepository) Create(ctx context.Context, itemID uuid.UUI
return mapMaintenanceEntryErr(item, err)
}
func (r *MaintenanceEntryRepository) Update(ctx context.Context, ID uuid.UUID, input MaintenanceEntryUpdate) (MaintenanceEntry, error) {
item, err := r.db.MaintenanceEntry.UpdateOneID(ID).
func (r *MaintenanceEntryRepository) Update(ctx context.Context, id uuid.UUID, input MaintenanceEntryUpdate) (MaintenanceEntry, error) {
item, err := r.db.MaintenanceEntry.UpdateOneID(id).
SetDate(input.CompletedDate.Time()).
SetScheduledDate(input.ScheduledDate.Time()).
SetName(input.Name).
@@ -130,78 +123,34 @@ func (r *MaintenanceEntryRepository) Update(ctx context.Context, ID uuid.UUID, i
return mapMaintenanceEntryErr(item, err)
}
type MaintenanceLogQuery struct {
Completed bool `json:"completed" schema:"completed"`
Scheduled bool `json:"scheduled" schema:"scheduled"`
}
func (r *MaintenanceEntryRepository) GetLog(ctx context.Context, groupID, itemID uuid.UUID, query MaintenanceLogQuery) (MaintenanceLog, error) {
log := MaintenanceLog{
ItemID: itemID,
}
q := r.db.MaintenanceEntry.Query().Where(
func (r *MaintenanceEntryRepository) GetMaintenanceByItemID(ctx context.Context, groupID, itemID uuid.UUID, filters MaintenanceFilters) ([]MaintenanceEntryWithDetails, error) {
query := r.db.MaintenanceEntry.Query().Where(
maintenanceentry.ItemID(itemID),
maintenanceentry.HasItemWith(
item.HasGroupWith(group.IDEQ(groupID)),
),
)
if query.Completed {
q = q.Where(maintenanceentry.And(
maintenanceentry.DateNotNil(),
maintenanceentry.DateNEQ(time.Time{}),
))
} else if query.Scheduled {
q = q.Where(maintenanceentry.And(
maintenanceentry.Or(
if filters.Status == MaintenanceFilterStatusScheduled {
query = query.Where(maintenanceentry.Or(
maintenanceentry.DateIsNil(),
maintenanceentry.DateEQ(time.Time{}),
),
maintenanceentry.ScheduledDateNotNil(),
maintenanceentry.ScheduledDateNEQ(time.Time{}),
))
} else if filters.Status == MaintenanceFilterStatusCompleted {
query = query.Where(
maintenanceentry.Not(maintenanceentry.Or(
maintenanceentry.DateIsNil(),
maintenanceentry.DateEQ(time.Time{})),
))
}
entries, err := query.WithItem().Order(maintenanceentry.ByScheduledDate()).All(ctx)
entries, err := q.Order(ent.Desc(maintenanceentry.FieldDate)).
All(ctx)
if err != nil {
return MaintenanceLog{}, err
return []MaintenanceEntryWithDetails{}, err
}
log.Entries = mapEachMaintenanceEntry(entries)
var maybeTotal *float64
var maybeAverage *float64
statement := `
SELECT
SUM(cost_total) AS total_of_totals,
AVG(cost_total) AS avg_of_averages
FROM
(
SELECT
strftime('%m-%Y', date) AS my,
SUM(cost) AS cost_total
FROM
maintenance_entries
WHERE
item_id = ?
GROUP BY
my
)`
row := r.db.Sql().QueryRowContext(ctx, statement, itemID)
err = row.Scan(&maybeTotal, &maybeAverage)
if err != nil {
return MaintenanceLog{}, err
}
log.CostAverage = orDefault(maybeAverage, 0)
log.CostTotal = orDefault(maybeTotal, 0)
return log, nil
return mapEachMaintenanceEntryWithDetails(entries), nil
}
func (r *MaintenanceEntryRepository) Delete(ctx context.Context, ID uuid.UUID) error {
return r.db.MaintenanceEntry.DeleteOneID(ID).Exec(ctx)
func (r *MaintenanceEntryRepository) Delete(ctx context.Context, id uuid.UUID) error {
return r.db.MaintenanceEntry.DeleteOneID(id).Exec(ctx)
}

View File

@@ -60,27 +60,14 @@ func TestMaintenanceEntryRepository_GetLog(t *testing.T) {
}
// Get the log for the item
log, err := tRepos.MaintEntry.GetLog(context.Background(), tGroup.ID, item.ID, MaintenanceLogQuery{
Completed: true,
})
log, err := tRepos.MaintEntry.GetMaintenanceByItemID(context.Background(), tGroup.ID, item.ID, MaintenanceFilters{Status: MaintenanceFilterStatusCompleted})
if err != nil {
t.Fatalf("failed to get maintenance log: %v", err)
}
assert.Equal(t, item.ID, log.ItemID)
assert.Len(t, log.Entries, 10)
assert.Len(t, log, 10)
// Calculate the average cost
var total float64
for _, entry := range log.Entries {
total += entry.Cost
}
assert.InDelta(t, total, log.CostTotal, .001, "total cost should be equal to the sum of all entries")
assert.InDelta(t, total/2, log.CostAverage, 001, "average cost should be the average of the two months")
for _, entry := range log.Entries {
for _, entry := range log {
err := tRepos.MaintEntry.Delete(context.Background(), entry.ID)
require.NoError(t, err)
}

View File

@@ -55,7 +55,7 @@ type (
Name string `json:"name"`
IsActive bool `json:"isActive"`
URL string `json:"-"` // URL field is not exposed to the client
URL string `json:"url"`
}
)
@@ -114,7 +114,7 @@ func (r *NotifierRepository) Update(ctx context.Context, userID uuid.UUID, id uu
return r.mapper.MapErr(notifier, err)
}
func (r *NotifierRepository) Delete(ctx context.Context, userID uuid.UUID, ID uuid.UUID) error {
_, err := r.db.Notifier.Delete().Where(notifier.UserID(userID), notifier.ID(ID)).Exec(ctx)
func (r *NotifierRepository) Delete(ctx context.Context, userID uuid.UUID, id uuid.UUID) error {
_, err := r.db.Notifier.Delete().Where(notifier.UserID(userID), notifier.ID(id)).Exec(ctx)
return err
}

View File

@@ -60,9 +60,9 @@ func mapUserOut(user *ent.User) UserOut {
}
}
func (r *UserRepository) GetOneID(ctx context.Context, ID uuid.UUID) (UserOut, error) {
func (r *UserRepository) GetOneID(ctx context.Context, id uuid.UUID) (UserOut, error) {
return mapUserOutErr(r.db.User.Query().
Where(user.ID(ID)).
Where(user.ID(id)).
WithGroup().
Only(ctx))
}
@@ -101,9 +101,9 @@ func (r *UserRepository) Create(ctx context.Context, usr UserCreate) (UserOut, e
return r.GetOneID(ctx, entUser.ID)
}
func (r *UserRepository) Update(ctx context.Context, ID uuid.UUID, data UserUpdate) error {
func (r *UserRepository) Update(ctx context.Context, id uuid.UUID, data UserUpdate) error {
q := r.db.User.Update().
Where(user.ID(ID)).
Where(user.ID(id)).
SetName(data.Name).
SetEmail(data.Email)
@@ -130,6 +130,6 @@ func (r *UserRepository) GetSuperusers(ctx context.Context) ([]*ent.User, error)
return users, nil
}
func (r *UserRepository) ChangePassword(ctx context.Context, UID uuid.UUID, pw string) error {
return r.db.User.UpdateOneID(UID).SetPassword(pw).Exec(ctx)
func (r *UserRepository) ChangePassword(ctx context.Context, uid uuid.UUID, pw string) error {
return r.db.User.UpdateOneID(uid).SetPassword(pw).Exec(ctx)
}

View File

@@ -1,7 +1,12 @@
import { defineConfig } from 'vitepress'
import enMenu from "./menus/en.mjs";
// https://vitepress.dev/reference/site-config
export default defineConfig({
ignoreDeadLinks: [
/^https?:\/\/localhost:7745/,
],
title: "HomeBox",
description: "A simple home inventory management software",
lastUpdated: true,
@@ -9,6 +14,15 @@ export default defineConfig({
hostname: 'https://homebox.software',
},
head: [
['link', { rel: 'icon', href: '/favicon.svg' }],
['meta', { name: 'theme-color', content: '#3eaf7c' }],
['meta', { name: 'og:title', content: 'HomeBox' }],
['meta', { name: 'og:description', content: 'A simple home inventory management software' }],
['meta', { name: 'og:image', content: '/homebox-email-banner.jpg' }],
['meta', { name: 'twitter:card', content: 'summary' }],
],
locales: {
en: {
label: 'English',
@@ -27,37 +41,17 @@ 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: {
'/en/': [
{
text: 'Getting Started',
items: [
{ text: 'Quick Start', link: '/en/quick-start' },
{ text: 'Tips and Tricks', link: '/en/tips-tricks' }
]
},
{
text: 'Advanced',
items: [
{ text: 'Import CSV', link: '/en/import-csv' },
]
},
{
text: 'Contributing',
items: [
{ text: 'Get Started', link: '/en/contribute/get-started' },
{ text: 'Bounty Program', link: '/en/contribute/bounty' }
]
}
]
'/en/': enMenu,
},
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

@@ -0,0 +1,25 @@
export default [
{
text: 'Getting Started',
items: [
{text: 'Quick Start', link: '/en/quick-start'},
{text: 'Installation', link: '/en/installation'},
{text: 'Organizing Your Items', link: '/en/organizing-items'},
{text: 'Configure Homebox', link: '/en/configure-homebox'},
{text: 'Tips and Tricks', link: '/en/tips-tricks'}
]
},
{
text: 'Advanced',
items: [
{text: 'Import CSV', link: '/en/import-csv'},
]
},
{
text: 'Contributing',
items: [
{text: 'Get Started', link: '/en/contribute/get-started'},
{text: 'Bounty Program', link: '/en/contribute/bounty'}
]
}
]

View File

@@ -910,14 +910,34 @@
"application/json"
],
"tags": [
"Maintenance"
"Item Maintenance"
],
"summary": "Get Maintenance Log",
"parameters": [
{
"enum": [
"scheduled",
"completed",
"both"
],
"type": "string",
"x-enum-varnames": [
"MaintenanceFilterStatusScheduled",
"MaintenanceFilterStatusCompleted",
"MaintenanceFilterStatusBoth"
],
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.MaintenanceLog"
"type": "array",
"items": {
"$ref": "#/definitions/repo.MaintenanceEntryWithDetails"
}
}
}
}
@@ -932,7 +952,7 @@
"application/json"
],
"tags": [
"Maintenance"
"Item Maintenance"
],
"summary": "Create Maintenance Entry",
"parameters": [
@@ -956,60 +976,6 @@
}
}
},
"/v1/items/{id}/maintenance/{entry_id}": {
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Update Maintenance Entry",
"parameters": [
{
"description": "Entry Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.MaintenanceEntryUpdate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.MaintenanceEntry"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Delete Maintenance Entry",
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/v1/items/{id}/path": {
"get": {
"security": [
@@ -1402,6 +1368,104 @@
}
}
},
"/v1/maintenance": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Query All Maintenance",
"parameters": [
{
"enum": [
"scheduled",
"completed",
"both"
],
"type": "string",
"x-enum-varnames": [
"MaintenanceFilterStatusScheduled",
"MaintenanceFilterStatusCompleted",
"MaintenanceFilterStatusBoth"
],
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.MaintenanceEntryWithDetails"
}
}
}
}
}
},
"/v1/maintenance/{id}": {
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Update Maintenance Entry",
"parameters": [
{
"description": "Entry Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/repo.MaintenanceEntryUpdate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/repo.MaintenanceEntry"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Maintenance"
],
"summary": "Delete Maintenance Entry",
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/v1/notifiers": {
"get": {
"security": [
@@ -2607,26 +2671,49 @@
}
}
},
"repo.MaintenanceLog": {
"repo.MaintenanceEntryWithDetails": {
"type": "object",
"properties": {
"costAverage": {
"type": "number"
"completedDate": {
"type": "string"
},
"costTotal": {
"type": "number"
"cost": {
"type": "string",
"example": "0"
},
"entries": {
"type": "array",
"items": {
"$ref": "#/definitions/repo.MaintenanceEntry"
}
"description": {
"type": "string"
},
"itemId": {
"id": {
"type": "string"
},
"itemID": {
"type": "string"
},
"itemName": {
"type": "string"
},
"name": {
"type": "string"
},
"scheduledDate": {
"type": "string"
}
}
},
"repo.MaintenanceFilterStatus": {
"type": "string",
"enum": [
"scheduled",
"completed",
"both"
],
"x-enum-varnames": [
"MaintenanceFilterStatusScheduled",
"MaintenanceFilterStatusCompleted",
"MaintenanceFilterStatusBoth"
]
},
"repo.NotifierCreate": {
"type": "object",
"required": [
@@ -2668,6 +2755,10 @@
"updatedAt": {
"type": "string"
},
"url": {
"description": "URL field is not exposed to the client",
"type": "string"
},
"userId": {
"type": "string"
}
@@ -2710,9 +2801,6 @@
},
"total": {
"type": "integer"
},
"totalPrice": {
"type": "number"
}
}
},

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

@@ -58,7 +58,12 @@ For documentation contributions, you only need Node.js and PNPM.
:::
## 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.
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. We accept translations for any language.
If you add a new language, please go to the English translation, press the `Add new translation string` button and then
use `languages.<language_code>` as the key. For example, if you are adding a French translation, the key would be `languages.fr`.
And then the string should be the name of the language in English. This is used to display the language in the language switcher.
[![Translation status](http://translate.sysadminsmedia.com/widget/homebox/multi-auto.svg)](http://translate.sysadminsmedia.com/engage/homebox/)

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

@@ -0,0 +1,80 @@
# Organizing Your Items
Homebox allows you to organize your items using locations and labels.
## Items
Items represent an item or asset you want to track in Homebox.
To create an item, click on the Create button in the main menu, the select Item / Asset. The following parameters are available:
- Parent Location (Required) - The location in which the item is stored
- Item Name (Required) - Name of the item
- Item Description (Optional) - Description of the item
- Labels (Optional) - Here, you can add as many Labels to the item as you like for further organization
- Photo (Optional) - Allows you to add a photo of the item. Additional photos can be added after the item has been created.
::: details Additional Fields
Once the item is created, additional fields become available. These are:
**Details Section**
- Quantity
- Serial Number
- Model Number
- Manufacturer
- Notes
- Insured (Checkbox)
- Archived (Checkbox)
- Asset-ID (Populated by default)
**Purchase Details Section**
- Purchased From
- Purchase Price
- Purchase Date
**Warranty Details Section**
- Lifetime Warranty (Checkbox)
- Warranty Expires
- Warranty Notes
**Sold Details Section**
- Sold to
- Sold Price
- Sold At
:::
You can also add custom fields by scrolling down to the Custom Fields section and clicking the add button. Custom fields are added on a per-item basis, not globally.
You might want to add attachments to an item (such as photos, a user manual, a warranty, a receipt, etc.) Scroll down to the Attachments section, and either click on the box or drag and drop to add files. Once a file has been added, click edit to edit its details.
> [!TIP]
> To set the primary image (the image that appears in the Search view) for the item, click on the edit button next to the uploaded image and ensure the Primary Photo checkbox is checked. By default, the first photo you upload will automatically be the Primary Photo.
## Locations
*Items* are stored in *locations*. Locations can be nested to create as many locations as you need. Homebox creates some default locations initially, but you can easily change, delete or rearrange them.
To create a location, click the Create button in the main menu, then select Location.
Locations have 3 parameters:
- Name
- Description (Optional)
- Parent Location (Optional -Leave blank if this should be a top-level Location, or select the location you want this location to be nested under)
To view all locations and the items stored in them, select Locations in the main menu.
## Labels
Labels allow you to organize items independently of location. For example, you might have electronic devices all over your house. What if you wanted to see a list of every Electronic Device you own without having to go through every single location?
Labels enable this. In the example above, if you tag all electronic devices with the "Electronics" label when you create them, you can easily see the list of all your Electronics by going to the related page.
To create a Label, click the Create button in the menu, then select Label. Labels have the following parameters:
- Name
- Description (Optional)
To see all items related to a specific label (or specific combination of labels):
- From the Home page, scroll to the bottom and select the Label you want to see.
- Alternatively, navigate to the Search page and use the Labels dropdown to filter by one or more labels.
Items can have as many labels as you wish. The example above is only one example of how labels can be used for organization. Experiment with what labels work best for you!

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

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

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

@@ -4,11 +4,11 @@
<p>
{{ $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"
>
@@ -30,10 +30,10 @@
<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" />
<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>

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 rounded-lg bg-base-100 shadow-xl">
<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-auto">
<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

@@ -1,15 +1,15 @@
<template>
<div v-if="!inline" class="form-control w-full">
<label class="label">
<span class="label-text"> {{ label }}</span>
<span class="label-text"> {{ label }} </span>
</label>
<VueDatePicker v-model="selected" :enable-time-picker="false" clearable :dark="isDark" />
<VueDatePicker v-model="selected" :enable-time-picker="false" clearable :dark="isDark" :teleport="true" />
</div>
<div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
<label class="label">
<span class="label-text"> {{ label }} </span>
</label>
<VueDatePicker v-model="selected" :enable-time-picker="false" clearable :dark="isDark" />
<VueDatePicker v-model="selected" :enable-time-picker="false" clearable :dark="isDark" :teleport="true" />
</div>
</template>

View File

@@ -4,33 +4,48 @@
<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>
<button
v-if="value.length > 0"
type="button"
class="absolute inset-y-0 right-6 flex items-center rounded-r-md px-2 focus:outline-none"
@click="clear"
>
<MdiClose class="size-5" />
</button>
</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"
>
<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 items"
v-for="(obj, idx) in filteredItems"
:key="idx"
:class="{
bordered: selected[idx],
bordered: selected.includes(obj[props.uniqueField]),
}"
>
<button type="button" @click="toggle(idx)">
<button type="button" @click="toggle(obj[props.uniqueField])">
{{ name != "" ? obj[name] : obj }}
</button>
</li>
</ul>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import MdiClose from "~icons/mdi/close";
const emit = defineEmits(["update:modelValue"]);
const props = defineProps({
label: {
@@ -49,6 +64,10 @@
type: String,
default: "name",
},
uniqueField: {
type: String,
default: "id",
},
selectFirst: {
type: Boolean,
default: false,
@@ -57,19 +76,30 @@
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);
function clear() {
value.value = [];
}
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

@@ -2,21 +2,35 @@
<div v-if="!inline" class="form-control w-full">
<label class="label">
<span class="label-text">{{ label }}</span>
<span
:class="{
'text-red-600':
typeof value === 'string' &&
((maxLength && value.length > maxLength) || (minLength && value.length < minLength)),
}"
>
{{ typeof value === "string" && (maxLength || minLength) ? `${value.length}/${maxLength}` : "" }}
</span>
</label>
<textarea ref="el" v-model="value" class="textarea w-full textarea-bordered h-28" :placeholder="placeholder" />
<label v-if="limit" class="label">
<span class="label-text-alt"></span>
<span class="label-text-alt"> {{ valueLen }}/{{ limit }}</span>
</label>
<textarea ref="el" v-model="value" class="textarea textarea-bordered h-28 w-full" :placeholder="placeholder" />
</div>
<div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
<label class="label">
<span class="label-text">{{ label }}</span>
<span
:class="{
'text-red-600':
typeof value === 'string' &&
((maxLength && value.length > maxLength) || (minLength && value.length < minLength)),
}"
>
{{ typeof value === "string" && (maxLength || minLength) ? `${value.length}/${maxLength}` : "" }}
</span>
</label>
<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
@@ -39,10 +53,6 @@
type: String,
default: "text",
},
limit: {
type: [Number, String],
default: null,
},
placeholder: {
type: String,
default: "",
@@ -51,6 +61,14 @@
type: Boolean,
default: false,
},
maxLength: {
type: Number,
required: false,
},
minLength: {
type: Number,
required: false,
},
});
const el = ref();

View File

@@ -1,15 +1,46 @@
<template>
<div v-if="!inline" class="form-control w-full">
<label class="label">
<span class="label-text">{{ label }}</span>
<span class="label-text"> {{ label }} </span>
<span
:class="{
'text-red-600':
typeof value === 'string' &&
((maxLength && value.length > maxLength) || (minLength && value.length < minLength)),
}"
>
{{ typeof value === "string" && (maxLength || minLength) ? `${value.length}/${maxLength}` : "" }}
</span>
</label>
<input ref="input" v-model="value" :placeholder="placeholder" :type="type" class="input input-bordered w-full" />
<input
ref="input"
v-model="value"
:placeholder="placeholder"
:type="type"
:required="required"
class="input input-bordered w-full"
/>
</div>
<div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
<label class="label">
<span class="label-text">{{ label }}</span>
<span class="label-text"> {{ label }} </span>
<span
:class="{
'text-red-600':
typeof value === 'string' &&
((maxLength && value.length > maxLength) || (minLength && value.length < minLength)),
}"
>
{{ typeof value === "string" && (maxLength || minLength) ? `${value.length}/${maxLength}` : "" }}
</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"
:type="type"
:required="required"
class="input input-bordered col-span-3 mt-2 w-full"
/>
</div>
</template>
@@ -23,6 +54,10 @@
type: [String, Number],
default: null,
},
required: {
type: [Boolean],
default: null,
},
type: {
type: String,
default: "text",
@@ -39,6 +74,14 @@
type: String,
default: "",
},
maxLength: {
type: Number,
required: false,
},
minLength: {
type: Number,
required: false,
},
});
const input = ref<HTMLElement | null>(null);

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

@@ -3,29 +3,37 @@
<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" />
<FormTextField
ref="nameInput"
v-model="form.name"
:trigger-focus="focused"
:autofocus="true"
label="Item Name"
:max-length="255"
:min-length="1"
/>
<FormTextArea v-model="form.description" label="Item Description" :max-length="1000" />
<FormMultiselect v-model="form.labels" label="Labels" :items="labels ?? []" />
<div class="modal-action">
<div class="flex justify-center">
<div class="modal-action mb-6">
<div>
<label for="photo" class="btn">{{ $t("components.item.create_modal.photo_button") }}</label>
<input type="file" accept="image/*" @change="previewImage" style="visibility:hidden;" id="photo">
<input id="photo" class="hidden" type="file" accept="image/png,image/jpeg,image/gif" @change="previewImage" />
</div>
<div class="grow"></div>
<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>
{{ $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)">{{ $t("global.create_and_add") }}</button>
</li>
@@ -34,18 +42,19 @@
</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 object-cover rounded-t shadow-sm border-gray-300" />
<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>
@@ -103,40 +112,25 @@
description: "",
color: "", // Future!
labels: [] as LabelOut[],
preview: null,
photo: null
preview: null as string | null,
photo: null as File | null,
});
const { shift } = useMagicKeys();
function previewImage(event) {
var input = event.target;
if (input.files) {
var reader = new FileReader();
reader.onload = (e) => {
form.preview = e.target.result;
}
form.photo=input.files[0];
reader.readAsDataURL(input.files[0]);
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);
}
}
function uploadImage(e: Event) {
const files = (e.target as HTMLInputElement).files;
if (!files || !files.item(0)) {
return;
}
const first = files.item(0);
if (!first) {
return;
}
uploadAttachment([first], null);
}
whenever(
() => modal.value,
() => {
@@ -160,6 +154,13 @@
return;
}
if (loading.value) {
toast.error("Already creating an item");
return;
}
loading.value = true;
if (shift.value) {
close = false;
}
@@ -175,6 +176,7 @@
const { error, data } = await api.items.create(out);
loading.value = false;
if (error) {
loading.value = false;
toast.error("Couldn't create item");
return;
}
@@ -182,10 +184,11 @@
toast.success("Item created");
// if the photo was provided, upload it
if(form.photo){
const { data2, error } = await api.items.attachments.add(data.id, form.photo, form.photo.name, AttachmentTypes.Photo);
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;
}
@@ -193,7 +196,6 @@
toast.success("Photo uploaded");
}
// Reset
form.name = "";
form.description = "";

View File

@@ -28,23 +28,23 @@
<template>
<section>
<BaseSectionHeader class="mb-2 flex justify-between items-center">
<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" />
<MdiCardTextOutline class="size-5" />
{{ $t("components.item.view.selectable.card") }}
</button>
</li>
<li>
<button @click="setViewPreference('table')">
<MdiTable class="h-5 w-5" />
<MdiTable class="size-5" />
{{ $t("components.item.view.selectable.table") }}
</button>
</li>
@@ -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">{{ $t("components.item.view.selectable.no_items") }}</div>
<div class="hidden 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

@@ -8,16 +8,18 @@
:trigger-focus="focused"
:autofocus="true"
label="Label Name"
:max-length="255"
:min-length="1"
/>
<FormTextArea v-model="form.description" label="Label Description" />
<FormTextArea v-model="form.description" label="Label Description" :max-length="255" />
<div class="modal-action">
<div class="flex justify-center">
<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)">{{ $t("global.create_and_add") }}</button>
</li>
@@ -26,7 +28,7 @@
</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 +73,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

@@ -7,18 +7,21 @@
v-model="form.name"
:trigger-focus="focused"
:autofocus="true"
:required="true"
label="Location Name"
:max-length="255"
:min-length="1"
/>
<FormTextArea v-model="form.description" label="Location Description" />
<FormTextArea v-model="form.description" label="Location Description" :max-length="1000" />
<LocationSelector v-model="form.parent" />
<div class="modal-action">
<div class="flex justify-center">
<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 bg-base-100 right-0 w-64 p-2 shadow">
<li>
<button type="button" @click="create(false)">{{ $t("global.create_and_add") }}</button>
</li>
@@ -27,7 +30,7 @@
</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 +76,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,10 @@
</script>
<template>
<div class="p-4 border-2 root">
<div class="root border-2 p-4">
<p v-if="locs.length === 0" class="text-center text-sm">
{{ $t("location.tree.no_locations") }}
</p>
<LocationTreeNode v-for="item in locs" :key="item.id" :item="item" :tree-id="treeId" />
</div>
</template>

View File

@@ -0,0 +1,164 @@
<template>
<BaseModal v-model="visible">
<template #title>
{{ entry.id ? $t("maintenance.modal.edit_title") : $t("maintenance.modal.new_title") }}
</template>
<form @submit.prevent="dispatchFormSubmit">
<FormTextField v-model="entry.name" autofocus :label="$t('maintenance.modal.entry_name')" />
<DatePicker v-model="entry.completedDate" :label="$t('maintenance.modal.completed_date')" />
<DatePicker v-model="entry.scheduledDate" :label="$t('maintenance.modal.scheduled_date')" />
<FormTextArea v-model="entry.description" :label="$t('maintenance.modal.notes')" />
<FormTextField v-model="entry.cost" autofocus :label="$t('maintenance.modal.cost')" />
<div class="flex justify-end py-2">
<BaseButton type="submit" class="ml-2 mt-2">
<template #icon>
<MdiPost />
</template>
{{ entry.id ? $t("maintenance.modal.edit_action") : $t("maintenance.modal.new_action") }}
</BaseButton>
</div>
</form>
</BaseModal>
</template>
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import type { MaintenanceEntry, MaintenanceEntryWithDetails } from "~~/lib/api/types/data-contracts";
import MdiPost from "~icons/mdi/post";
import DatePicker from "~~/components/Form/DatePicker.vue";
const { t } = useI18n();
const api = useUserApi();
const toast = useNotifier();
const emit = defineEmits(["changed"]);
const visible = ref(false);
const entry = reactive({
id: null as string | null,
name: "",
completedDate: null as Date | null,
scheduledDate: null as Date | null,
description: "",
cost: "",
itemId: null as string | null,
});
async function dispatchFormSubmit() {
if (entry.id) {
await editEntry();
return;
}
await createEntry();
}
async function createEntry() {
if (!entry.itemId) {
return;
}
const { error } = await api.items.maintenance.create(entry.itemId, {
name: entry.name,
completedDate: entry.completedDate ?? "",
scheduledDate: entry.scheduledDate ?? "",
description: entry.description,
cost: parseFloat(entry.cost) ? entry.cost : "0",
});
if (error) {
toast.error(t("maintenance.toast.failed_to_create"));
return;
}
visible.value = false;
emit("changed");
}
async function editEntry() {
if (!entry.id) {
return;
}
const { error } = await api.maintenance.update(entry.id, {
name: entry.name,
completedDate: entry.completedDate ?? "null",
scheduledDate: entry.scheduledDate ?? "null",
description: entry.description,
cost: entry.cost,
});
if (error) {
toast.error(t("maintenance.toast.failed_to_update"));
return;
}
visible.value = false;
emit("changed");
}
const openCreateModal = (itemId: string) => {
entry.id = null;
entry.name = "";
entry.completedDate = null;
entry.scheduledDate = null;
entry.description = "";
entry.cost = "";
entry.itemId = itemId;
visible.value = true;
};
const openUpdateModal = (maintenanceEntry: MaintenanceEntry | MaintenanceEntryWithDetails) => {
entry.id = maintenanceEntry.id;
entry.name = maintenanceEntry.name;
entry.completedDate = new Date(maintenanceEntry.completedDate);
entry.scheduledDate = new Date(maintenanceEntry.scheduledDate);
entry.description = maintenanceEntry.description;
entry.cost = maintenanceEntry.cost;
entry.itemId = null;
visible.value = true;
};
const confirm = useConfirm();
async function deleteEntry(id: string) {
const result = await confirm.open(t("maintenance.modal.delete_confirmation"));
if (result.isCanceled) {
return;
}
const { error } = await api.maintenance.delete(id);
if (error) {
toast.error(t("maintenance.toast.failed_to_delete"));
return;
}
emit("changed");
}
async function complete(maintenanceEntry: MaintenanceEntry) {
const { error } = await api.maintenance.update(maintenanceEntry.id, {
name: maintenanceEntry.name,
completedDate: new Date(Date.now()),
scheduledDate: maintenanceEntry.scheduledDate ?? "null",
description: maintenanceEntry.description,
cost: maintenanceEntry.cost,
});
if (error) {
toast.error(t("maintenance.toast.failed_to_update"));
}
emit("changed");
}
function duplicate(maintenanceEntry: MaintenanceEntry | MaintenanceEntryWithDetails, itemId: string) {
entry.id = null;
entry.name = maintenanceEntry.name;
entry.completedDate = null;
entry.scheduledDate = null;
entry.description = maintenanceEntry.description;
entry.cost = maintenanceEntry.cost;
entry.itemId = itemId;
visible.value = true;
}
defineExpose({ openCreateModal, openUpdateModal, deleteEntry, complete, duplicate });
</script>

View File

@@ -0,0 +1,201 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import type { MaintenanceEntryWithDetails } from "~~/lib/api/types/data-contracts";
import { MaintenanceFilterStatus } from "~~/lib/api/types/data-contracts";
import type { StatsFormat } from "~~/components/global/StatCard/types";
import MdiCheck from "~icons/mdi/check";
import MdiDelete from "~icons/mdi/delete";
import MdiEdit from "~icons/mdi/edit";
import MdiCalendar from "~icons/mdi/calendar";
import MdiPlus from "~icons/mdi/plus";
import MdiWrenchClock from "~icons/mdi/wrench-clock";
import MdiContentDuplicate from "~icons/mdi/content-duplicate";
import MaintenanceEditModal from "~~/components/Maintenance/EditModal.vue";
const maintenanceFilterStatus = ref(MaintenanceFilterStatus.MaintenanceFilterStatusScheduled);
const maintenanceEditModal = ref<InstanceType<typeof MaintenanceEditModal>>();
const api = useUserApi();
const { t } = useI18n();
const props = defineProps({
currentItemId: {
type: String,
default: undefined,
},
});
const { data: maintenanceDataList, refresh: refreshList } = useAsyncData<MaintenanceEntryWithDetails[]>(
async () => {
const { data } =
props.currentItemId !== undefined
? await api.items.maintenance.getLog(props.currentItemId, { status: maintenanceFilterStatus.value })
: await api.maintenance.getAll({ status: maintenanceFilterStatus.value });
console.log(data);
return data as MaintenanceEntryWithDetails[];
},
{
watch: maintenanceFilterStatus,
}
);
const stats = computed(() => {
console.log(maintenanceDataList);
if (!maintenanceDataList.value) return [];
const count = maintenanceDataList.value ? maintenanceDataList.value.length || 0 : 0;
let total = 0;
maintenanceDataList.value.forEach(item => {
total += parseFloat(item.cost);
});
const average = count > 0 ? total / count : 0;
return [
{
id: "count",
title: t("maintenance.total_entries"),
value: count,
type: "number" as StatsFormat,
},
{
id: "total",
title: t("maintenance.total_cost"),
value: total,
type: "currency" as StatsFormat,
},
{
id: "average",
title: t("maintenance.monthly_average"),
value: average,
type: "currency" as StatsFormat,
},
];
});
</script>
<template>
<section class="space-y-6">
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
<StatCard
v-for="stat in stats"
:key="stat.id"
class="stats border-l-primary block shadow-xl"
:title="stat.title"
:value="stat.value"
:type="stat.type"
/>
</div>
<div class="flex">
<div class="btn-group">
<BaseButton
size="sm"
:class="`${maintenanceFilterStatus == MaintenanceFilterStatus.MaintenanceFilterStatusScheduled ? 'btn-active' : ''}`"
@click="maintenanceFilterStatus = MaintenanceFilterStatus.MaintenanceFilterStatusScheduled"
>
{{ $t("maintenance.filter.scheduled") }}
</BaseButton>
<BaseButton
size="sm"
:class="`${maintenanceFilterStatus == MaintenanceFilterStatus.MaintenanceFilterStatusCompleted ? 'btn-active' : ''}`"
@click="maintenanceFilterStatus = MaintenanceFilterStatus.MaintenanceFilterStatusCompleted"
>
{{ $t("maintenance.filter.completed") }}
</BaseButton>
<BaseButton
size="sm"
:class="`${maintenanceFilterStatus == MaintenanceFilterStatus.MaintenanceFilterStatusBoth ? 'btn-active' : ''}`"
@click="maintenanceFilterStatus = MaintenanceFilterStatus.MaintenanceFilterStatusBoth"
>
{{ $t("maintenance.filter.both") }}
</BaseButton>
</div>
<BaseButton
v-if="props.currentItemId"
class="ml-auto"
size="sm"
@click="maintenanceEditModal?.openCreateModal(props.currentItemId)"
>
<template #icon>
<MdiPlus />
</template>
{{ $t("maintenance.list.new") }}
</BaseButton>
</div>
</section>
<section>
<!-- begin -->
<MaintenanceEditModal ref="maintenanceEditModal" @changed="refreshList"></MaintenanceEditModal>
<div class="container space-y-6">
<BaseCard v-for="e in maintenanceDataList" :key="e.id">
<BaseSectionHeader class="border-b border-b-gray-300 p-6">
<span class="text-base-content">
<span v-if="!props.currentItemId">
<NuxtLink class="hover:underline" :to="`/item/${(e as MaintenanceEntryWithDetails).itemID}`">
{{ (e as MaintenanceEntryWithDetails).itemName }}
</NuxtLink>
-
</span>
{{ e.name }}
</span>
<template #description>
<div class="flex flex-wrap gap-2">
<div v-if="validDate(e.completedDate)" class="badge p-3">
<MdiCheck class="mr-2" />
<DateTime :date="e.completedDate" format="human" datetime-type="date" />
</div>
<div v-else-if="validDate(e.scheduledDate)" class="badge p-3">
<MdiCalendar class="mr-2" />
<DateTime :date="e.scheduledDate" format="human" datetime-type="date" />
</div>
<div class="tooltip tooltip-primary" data-tip="Cost">
<div class="badge badge-primary p-3">
<Currency :amount="e.cost" />
</div>
</div>
</div>
</template>
</BaseSectionHeader>
<div class="p-6">
<Markdown :source="e.description" />
</div>
<div class="flex flex-wrap justify-end gap-1 p-4">
<BaseButton size="sm" @click="maintenanceEditModal?.openUpdateModal(e)">
<template #icon>
<MdiEdit />
</template>
{{ $t("maintenance.list.edit") }}
</BaseButton>
<BaseButton v-if="!validDate(e.completedDate)" size="sm" @click="maintenanceEditModal?.complete(e)">
<template #icon>
<MdiCheck />
</template>
{{ $t("maintenance.list.complete") }}
</BaseButton>
<BaseButton size="sm" @click="maintenanceEditModal?.duplicate(e, e.itemID)">
<template #icon>
<MdiContentDuplicate />
</template>
{{ $t("maintenance.list.duplicate") }}
</BaseButton>
<BaseButton size="sm" class="btn-error" @click="maintenanceEditModal?.deleteEntry(e.id)">
<template #icon>
<MdiDelete />
</template>
{{ $t("maintenance.list.delete") }}
</BaseButton>
</div>
</BaseCard>
<div v-if="props.currentItemId" class="hidden first:block">
<button
type="button"
class="border-base-content relative block w-full rounded-lg border-2 border-dashed p-12 text-center"
@click="maintenanceEditModal?.openCreateModal(props.currentItemId)"
>
<MdiWrenchClock class="inline size-16" />
<span class="mt-2 block text-sm font-medium text-gray-900"> {{ $t("maintenance.list.create_first") }} </span>
</button>
</div>
</div>
</section>
</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>
@@ -27,17 +27,17 @@
</ClientOnly>
</template>
<template v-else>
<span class="flex items-center">
<span class="flex items-center text-wrap">
{{ 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

@@ -24,7 +24,7 @@
</script>
<template>
<div class="markdown" v-html="raw"></div>
<div class="markdown text-wrap" v-html="raw"></div>
</template>
<style scoped>

View File

@@ -5,7 +5,7 @@
<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">{{ $t("components.global.page_qr_code.page_url") }}</h2>
<img :src="getQRCodeUrl()" />

View File

@@ -2,7 +2,7 @@
<div class="py-4">
<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

@@ -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

@@ -12,24 +12,24 @@
<AppToast />
<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" />
<div class="drawer-content bg-base-300 justify-center pt-20 lg:pt-0">
<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 bg-primary fixed top-0 z-[99] 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="text-base-100 flex text-3xl font-bold tracking-tight">
HomeB
<AppLogo class="w-8 -mb-3" />
<AppLogo class="-mb-3 w-8" />
x
</h2>
</NuxtLink>
</div>
<slot></slot>
<footer v-if="status" class="text-center w-full bottom-0 pb-4 bg-base-300 text-secondary-content">
<footer v-if="status" class="bg-base-300 text-secondary-content bottom-0 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 }) }}
@@ -42,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="bg-base-200 flex min-w-40 max-w-min flex-col p-5 md:py-10">
<div class="space-y-8">
<div class="flex flex-col items-center gap-4">
<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="bg-base-300 text-neutral-content w-24 rounded-full p-4">
<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="bg-base-200 flex flex-col">
<div class="mb-6">
<div class="dropdown visible w-full">
<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>
{{ $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 bg-base-100 w-full p-2 shadow">
<li v-for="btn in dropdown" :key="btn.name">
<button @click="btn.action">
{{ btn.name }}
@@ -70,7 +70,7 @@
</ul>
</div>
</div>
<ul class="flex flex-col mx-auto gap-2 w-40 menu">
<ul class="menu mx-auto flex flex-col gap-2">
<li v-for="n in nav" :key="n.id" class="text-xl" @click="unfocus">
<NuxtLink
v-if="n.to"
@@ -80,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>
@@ -89,7 +89,7 @@
</div>
<!-- Bottom -->
<button class="mt-auto mx-2 hover:bg-base-300 p-3 rounded-btn" @click="logout">
<button class="rounded-btn hover:bg-base-300 mx-2 mt-auto p-3" @click="logout">
{{ $t("global.sign_out") }}
</button>
</div>
@@ -99,6 +99,7 @@
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { useLabelStore } from "~~/stores/labels";
import { useLocationStore } from "~~/stores/locations";
import MdiMenu from "~icons/mdi/menu";
@@ -109,8 +110,13 @@
import MdiMagnify from "~icons/mdi/magnify";
import MdiAccount from "~icons/mdi/account";
import MdiCog from "~icons/mdi/cog";
import MdiWrench from "~icons/mdi/wrench";
const { t } = useI18n();
const username = computed(() => authCtx.user?.name || "User");
const preferences = useViewPreferences();
const pubApi = usePublicApi();
const { data: status } = useAsyncData(async () => {
const { data } = await pubApi.status();
@@ -162,35 +168,42 @@
icon: MdiHome,
active: computed(() => route.path === "/home"),
id: 0,
name: "Home",
name: t("menu.home"),
to: "/home",
},
{
icon: MdiFileTree,
id: 4,
id: 1,
active: computed(() => route.path === "/locations"),
name: "Locations",
name: t("menu.locations"),
to: "/locations",
},
{
icon: MdiMagnify,
id: 3,
id: 2,
active: computed(() => route.path === "/items"),
name: "Search",
name: t("menu.search"),
to: "/items",
},
{
icon: MdiWrench,
id: 3,
active: computed(() => route.path === "/maintenance"),
name: t("menu.maintenance"),
to: "/maintenance",
},
{
icon: MdiAccount,
id: 1,
id: 4,
active: computed(() => route.path === "/profile"),
name: "Profile",
name: t("menu.profile"),
to: "/profile",
},
{
icon: MdiCog,
id: 6,
id: 5,
active: computed(() => route.path === "/tools"),
name: "Tools",
name: t("menu.tools"),
to: "/tools",
},
];

View File

@@ -10,10 +10,10 @@ import type {
ItemUpdate,
MaintenanceEntry,
MaintenanceEntryCreate,
MaintenanceEntryUpdate,
MaintenanceLog,
MaintenanceEntryWithDetails,
} from "../types/data-contracts";
import type { AttachmentTypes, PaginationResult } from "../types/non-generated";
import type { MaintenanceFilters } from "./maintenance.ts";
import type { Requests } from "~~/lib/requests";
export type ItemsQuery = {
@@ -66,14 +66,11 @@ export class FieldsAPI extends BaseAPI {
}
}
type MaintenanceEntryQuery = {
scheduled?: boolean;
completed?: boolean;
};
export class MaintenanceAPI extends BaseAPI {
getLog(itemId: string, q: MaintenanceEntryQuery = {}) {
return this.http.get<MaintenanceLog>({ url: route(`/items/${itemId}/maintenance`, q) });
export class ItemMaintenanceAPI extends BaseAPI {
getLog(itemId: string, filters: MaintenanceFilters = {}) {
return this.http.get<MaintenanceEntryWithDetails[]>({
url: route(`/items/${itemId}/maintenance`, { status: filters.status?.toString() }),
});
}
create(itemId: string, data: MaintenanceEntryCreate) {
@@ -82,29 +79,18 @@ export class MaintenanceAPI extends BaseAPI {
body: data,
});
}
delete(itemId: string, entryId: string) {
return this.http.delete<void>({ url: route(`/items/${itemId}/maintenance/${entryId}`) });
}
update(itemId: string, entryId: string, data: MaintenanceEntryUpdate) {
return this.http.put<MaintenanceEntryUpdate, MaintenanceEntry>({
url: route(`/items/${itemId}/maintenance/${entryId}`),
body: data,
});
}
}
export class ItemsApi extends BaseAPI {
attachments: AttachmentsAPI;
maintenance: MaintenanceAPI;
maintenance: ItemMaintenanceAPI;
fields: FieldsAPI;
constructor(http: Requests, token: string) {
super(http, token);
this.fields = new FieldsAPI(http);
this.attachments = new AttachmentsAPI(http);
this.maintenance = new MaintenanceAPI(http);
this.maintenance = new ItemMaintenanceAPI(http);
}
fullpath(id: string) {

View File

@@ -0,0 +1,30 @@
import { BaseAPI, route } from "../base";
import type {
MaintenanceEntry,
MaintenanceEntryWithDetails,
MaintenanceEntryUpdate,
MaintenanceFilterStatus,
} from "../types/data-contracts";
export interface MaintenanceFilters {
status?: MaintenanceFilterStatus;
}
export class MaintenanceAPI extends BaseAPI {
getAll(filters: MaintenanceFilters) {
return this.http.get<MaintenanceEntryWithDetails[]>({
url: route(`/maintenance`, { status: filters.status?.toString() }),
});
}
delete(id: string) {
return this.http.delete<void>({ url: route(`/maintenance/${id}`) });
}
update(id: string, data: MaintenanceEntryUpdate) {
return this.http.put<MaintenanceEntryUpdate, MaintenanceEntry>({
url: route(`/maintenance/${id}`),
body: data,
});
}
}

View File

@@ -288,11 +288,22 @@ export interface MaintenanceEntryUpdate {
scheduledDate: Date | string;
}
export interface MaintenanceLog {
costAverage: number;
costTotal: number;
entries: MaintenanceEntry[];
itemId: string;
export interface MaintenanceEntryWithDetails {
completedDate: Date | string;
/** @example "0" */
cost: string;
description: string;
id: string;
itemID: string;
itemName: string;
name: string;
scheduledDate: Date | string;
}
export enum MaintenanceFilterStatus {
MaintenanceFilterStatusScheduled = "scheduled",
MaintenanceFilterStatusCompleted = "completed",
MaintenanceFilterStatusBoth = "both",
}
export interface NotifierCreate {
@@ -312,6 +323,8 @@ export interface NotifierOut {
isActive: boolean;
name: string;
updatedAt: Date | string;
/** URL field is not exposed to the client */
url: string;
userId: string;
}
@@ -330,7 +343,6 @@ export interface PaginationResultItemSummary {
page: number;
pageSize: number;
total: number;
totalPrice: number;
}
export interface TotalsByOrganizer {

View File

@@ -9,12 +9,14 @@ import { StatsAPI } from "./classes/stats";
import { AssetsApi } from "./classes/assets";
import { ReportsAPI } from "./classes/reports";
import { NotifiersAPI } from "./classes/notifiers";
import { MaintenanceAPI } from "./classes/maintenance";
import type { Requests } from "~~/lib/requests";
export class UserClient extends BaseAPI {
locations: LocationsApi;
labels: LabelsApi;
items: ItemsApi;
maintenance: MaintenanceAPI;
group: GroupApi;
user: UserApi;
actions: ActionsAPI;
@@ -29,6 +31,7 @@ export class UserClient extends BaseAPI {
this.locations = new LocationsApi(requests);
this.labels = new LabelsApi(requests);
this.items = new ItemsApi(requests, attachmentToken);
this.maintenance = new MaintenanceAPI(requests);
this.group = new GroupApi(requests);
this.user = new UserApi(requests);
this.actions = new ActionsAPI(requests);

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

@@ -0,0 +1,182 @@
{
"components": {
"app": {
"import_dialog": {
"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.",
"description": "Importa un fitxer CSV que contingui els articles, etiquetes i ubicacions. \nConsulteu la documentació per a més informació sobre el format requerit.",
"title": "Importa un fitxer CSV",
"upload": "Puja"
}
},
"global": {
"page_qr_code": {
"page_url": "URL de la pàgina"
},
"password_score": {
"password_strength": "Força de la contrasenya"
}
},
"item": {
"create_modal": {
"photo_button": "Foto 📷",
"title": "Crea un article"
},
"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": {
"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",
"labels": "Etiquetes",
"locations": "Ubicacions",
"name": "Nom",
"password": "Contrasenya",
"read_docs": "Llegiu la documentació",
"search": "Cerca",
"sign_out": "Tanca la sessió",
"submit": "Envia",
"version": "Versió {version}",
"welcome": "Us donem la benvinguda, { username }"
},
"index": {
"disabled_registration": "El registre és desactivat",
"dont_join_group": "Voleu unir-vos al grup?",
"joining_group": "Us uniu a un grup existent!",
"login": "Inici de sessió",
"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": {
"add": "Afegeix",
"created_at": "Creat a",
"custom_fields": "Camps personalitzats",
"field_selector": "Selector del camp",
"field_value": "Valor del camp",
"first": "Primer",
"include_archive": "Inclou els articles arxivats",
"last": "Últim",
"negate_labels": "Nega les etiquetes seleccionades",
"next_page": "Pàgina següent",
"no_results": "No s'ha trobat cap element",
"options": "Opcions",
"order_by": "Ordena per",
"pages": "Pàgina { page } de { totalPages }",
"prev_page": "Pàgina anterior",
"query_id": "S'està consultant el número d'identificació de l'actiu: { id }",
"reset_search": "Reinicia la cerca",
"results": "{ total } resultats",
"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.",
"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"
},
"labels": {
"no_results": "No s'han trobat etiquetes"
},
"languages": {
"ca": "Català",
"de": "Alemany",
"en": "Anglès",
"es": "Castellà",
"fr": "Francès",
"hu": "Hongarès",
"it": "Italià",
"nl": "Neerlandès",
"pl": "Polonès",
"pt-BR": "Portuguès (Brasil)",
"ru": "Rus",
"sl": "Eslovè",
"sv": "Suec",
"tr": "Turc",
"zh-CN": "Xinès (simplificat)",
"zh-HK": "Xinès (Hong Kong)",
"zh-MO": "Xinès (Macau)",
"zh-TW": "Xinès (tradicional)"
},
"locations": {
"no_results": "No s'han trobat ubicacions"
},
"maintenance": {
"filter": {
"both": "Ambdós"
},
"total_cost": "Cost total",
"total_entries": "Nombre d&apos; entrades"
},
"menu": {
"home": "Inici",
"locations": "Ubicacions",
"maintenance": "Manteniment",
"profile": "Perfil",
"search": "Cerca",
"tools": "Eines"
},
"profile": {
"active": "Actiu",
"change_password": "Canvia contrasenya",
"currency_format": "Format de moneda",
"current_password": "Contrasenya actual",
"delete_account": "Suprimeix el compte",
"delete_account_sub": "Elimina el compte i totes les dades associades. Aquesta acció no es pot desfer.",
"display_header": "{ currentValue, select, true {Amaga la capçalera} false {Mostra la capçalera} other {Desconegut}}",
"enabled": "Habilitat",
"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ó.",
"inactive": "Inactiu",
"language": "Idioma",
"new_password": "Contrasenya nova",
"no_notifiers": "No hi ha notificadors configurats",
"notifier_modal": "{ type, select, true {Edita} false {Crea} other {Altres}} Notificació",
"notifiers": "Notificadors",
"notifiers_sub": "Rebeu notificacions per als pròxims recordatoris de manteniment",
"test": "Prova",
"theme_settings": "Configuracions del tema",
"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.",
"update_group": "Actualitza el grup",
"update_language": "Actualitza l'idioma",
"url": "URL",
"user_profile": "Perfil d'usuari",
"user_profile_sub": "Convida usuaris i gestiona el compte."
},
"tools": {
"actions": "Accions d'inventari",
"actions_set": {
"ensure_ids": "Assegura els identificadors de recursos",
"ensure_ids_button": "Assegura els identificadors de recursos",
"set_primary_photo": "Defineix la foto principal",
"set_primary_photo_button": "Defineix la foto principal"
}
}
}

View File

@@ -2,9 +2,9 @@
"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, wird der Gegenstand mit den Werten in der CSV-Datei aktualisiert.",
"description": "Importiere eine CSV-Datei, die deine Gegenstände, Etiketten und Standorte enthält. Siehe Dokumentation für weitere Informationen zum \nerforderlichen Format.",
"title": "Importiere CSV-Datei",
"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"
}
},
@@ -18,7 +18,8 @@
},
"item": {
"create_modal": {
"title": "Erstelle Gegenstand"
"photo_button": "Foto 📷",
"title": "Gegenstand erstellen"
},
"view": {
"selectable": {
@@ -31,12 +32,15 @@
},
"label": {
"create_modal": {
"title": "Erstelle Etikette"
"title": "Etikett erstellen"
}
},
"location": {
"create_modal": {
"title": "Erstelle Standort"
"title": "Standort erstellen"
},
"tree": {
"no_locations": "Keine Standorte verfügbar. Fügen Sie neue Standorte über die Schaltfläche\n `<`span class=\"link-primary\"`>`Erstellen`<`/span`>` in der Navigationsleiste hinzu."
}
}
},
@@ -52,11 +56,11 @@
"items": "Gegenstände",
"join_discord": "Discord beitreten",
"labels": "Etiketten",
"locations": "Standorte",
"locations": "Lagerorte",
"name": "Name",
"password": "Passwort",
"read_docs": "Dokumentation lesen",
"search": "Suchen",
"search": "Suche",
"sign_out": "Abmelden",
"submit": "Einreichen",
"version": "Version: { version }",
@@ -81,12 +85,12 @@
"field_selector": "Feldauswahl",
"field_value": "Feldwert",
"first": "Erste",
"include_archive": "Archivierte Elemente einbeziehen",
"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",
"options": "Optionen (Options)",
"order_by": "Sortieren nach",
"pages": "Seite { page } von { totalPages }",
"prev_page": "Vorherige Seite",
@@ -100,28 +104,139 @@
"tips_sub": "Suchtipps",
"updated_at": "Aktualisiert am"
},
"labels": {
"no_results": "Keine Labels gefunden"
},
"languages": {
"ca": "Katalanisch",
"de": "Deutsch",
"en": "Englisch",
"es": "Spanisch",
"fr": "Französisch",
"hu": "Ungarisch",
"it": "Italienisch",
"nl": "Niederländisch",
"pl": "Polnisch",
"pt-BR": "Portugiesisch (Brasilien)",
"ru": "Russisch",
"sl": "Slowenisch",
"sv": "Schwedisch",
"tr": "Türkisch",
"zh-CN": "Chinesisch (einfach)",
"zh-HK": "Chinesisch (Hong Kong)",
"zh-MO": "Chinesisch (Macao)",
"zh-TW": "Chinesisch (Traditionell)"
},
"locations": {
"no_results": "Keine Standorte gefunden"
},
"maintenance": {
"filter": {
"both": "Beide",
"completed": "Abgeschlossen",
"scheduled": "Geplant"
},
"list": {
"complete": "Vollständig",
"create_first": "Erstellen Sie Ihren ersten Eintrag",
"delete": "Löschen",
"duplicate": "Duplizieren",
"edit": "Bearbeiten",
"new": "Neu"
},
"modal": {
"completed_date": "Fertigstellungsdatum",
"cost": "Kosten",
"delete_confirmation": "Sind Sie sicher, dass Sie diesen Eintrag löschen wollen?",
"edit_action": "Aktualisieren",
"edit_title": "Eintrag bearbeiten",
"entry_name": "Eingabename",
"new_action": "Erstellen",
"new_title": "Neuer Eintrag",
"notes": "Anmerkungen",
"scheduled_date": "Geplantes Datum"
},
"monthly_average": "Monatsdurchschnitt",
"toast": {
"failed_to_create": "Eintrag konnte nicht erstellt werden",
"failed_to_delete": "Eintrag konnte nicht gelöscht werden",
"failed_to_update": "Fehler beim Aktualisieren des Eintrags."
},
"total_cost": "Gesamtkosten",
"total_entries": "Gesamteinträge"
},
"menu": {
"home": "Home",
"locations": "Lagerorte",
"maintenance": "Wartung",
"profile": "Profil",
"search": "Suche",
"tools": "Tools"
},
"profile": {
"active": "Aktiv",
"change_password": "Passwort ändern",
"currency_format": "Währungsformat",
"current_password": "Aktuelles Passwort",
"delete_account": "Konto löschen",
"delete_account_sub": "Löschen Sie Ihr Konto und alle zugehörigen Daten. Dies kann nicht rückgängig gemacht werden.",
"delete_account_sub": "Lösche dein Konto und alle zugehörigen Daten. Dies kann nicht rückgängig gemacht werden.",
"display_header": "{ currentValue, select, true {Header ausblenden} false {Header anzeigen} other {Kein Treffer}}",
"enabled": "Aktiviert",
"gen_invite": "Einladungslink generieren",
"group_settings": "Gruppeneinstellungen",
"group_settings_sub": "Geteilte Gruppeneinstellungen. Möglicherweise müssen Sie Ihren Browser aktualisieren, damit einige Einstellungen wirksam werden.",
"group_settings_sub": "Gemeinsame Gruppeneinstellungen. Möglicherweise müssen Sie Ihren Browser aktualisieren, damit die Einstellungen wirksam werden.",
"inactive": "Inaktiv",
"language": "Sprache",
"new_password": "Neues Passwort",
"notifier_modal": "{ type, select, true {Bearbeiten} false {Erstellen} other {Andere}} Melder",
"no_notifiers": "Keine Benachrichtigungen konfiguriert",
"notifier_modal": "{ type, select, true {Bearbeiten} false {Erstellen} other {Andere}} Notifier",
"notifiers": "Melder",
"notifiers_sub": "Erhalten Sie Benachrichtigungen für bevorstehende Wartungserinnerungen",
"notifiers_sub": "Erhalte Benachrichtigungen über bevorstehende Wartungserinnerungen",
"test": "Test",
"theme_settings": "Themeneinstellungen",
"theme_settings_sub": "Themeneinstellungen werden im lokalen Speicher Ihres Browsers gespeichert. Sie können das Thema jederzeit ändern. Wenn Sie Probleme haben, Ihr Thema einzustellen, versuchen Sie, Ihren Browser zu aktualisieren.",
"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",
"update_language": "Sprache aktualisieren",
"url": "URL",
"user_profile": "Benutzerprofil",
"user_profile_sub": "Benutzer einladen und Ihr Konto verwalten."
"user_profile_sub": "Lade Benutzer ein und verwalte dein Konto."
},
"tools": {
"actions": "Inventar Aktionen",
"actions_set": {
"ensure_ids": "Sicherstellen von Asset-IDs",
"ensure_ids_button": "Sicherstellen von Asset-IDs",
"ensure_ids_sub": "Es wird sichergestellt, dass alle Artikel in Ihrem Bestand ein gültiges asset_id-Feld haben. Dazu wird das Feld mit der höchsten aktuellen asset_id in der Datenbank ermittelt und der nächste Wert auf jeden Artikel mit einem nicht gesetzten asset_id-Feld angewendet. Dies geschieht in der Reihenfolge des Feldes created_at.",
"ensure_import_refs": "Sicherstellen, dass Import-Referenzen importiert wurden",
"ensure_import_refs_button": "Sicherstellen, dass Referenzen importiert werden",
"ensure_import_refs_sub": "Es wird sichergestellt, dass alle Gegenstände in Ihrem Inventar ein gültiges import_ref-Feld haben. Dazu wird nach dem Zufallsprinzip eine 8-stellige Zeichenkette für jeden Gegenstand mit einem ungültigen import_ref-Feld erzeugt.",
"set_primary_photo": "Primärfoto festlegen",
"set_primary_photo_button": "Primäres Foto festlegen",
"set_primary_photo_sub": "In Homebox Version v0.10.0 wurde die Auswahl des Primärbilds von angehängten Bildern hinzugefügt. Mit dieser Funktion wird für jeden Artikel ohne Primärbild das erste angehängt Bild als solches definiert. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/pull/576\">'See GitHub PR #576'</a>'",
"zero_datetimes": "Null-Punkt-Datum-Zeiten",
"zero_datetimes_button": "Null-Punkt-Datum-Zeiten",
"zero_datetimes_sub": "Setzt den Zeitwert für alle Datums-Zeit-Felder in Ihrem Inventar auf den Anfang des Datums zurück. Damit wird ein Fehler behoben, der zu Beginn der Entwicklung der Website eingeführt wurde und dazu führte, dass das Datum zusammen mit der Uhrzeit gespeichert wurde, was zu Problemen bei der Anzeige von genauen Werten in Datumsfeldern führte. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/issues/236\" target=\"_blank\">'See Github Issue #236 for more details.'</a>'"
},
"actions_sub": "Aktionen in großen Mengen auf das Inventar anwenden. Diese Aktionen sind unumkehrbar. '<b>'Sei vorsichtig.'</b>'",
"import_export": "Import/Export",
"import_export_set": {
"export": "Gesamten Bestand exportieren",
"export_button": "Gesamten Bestand exportieren",
"export_sub": "Exportiert das Standard-CSV-Format für Homebox",
"import": "Inventar importieren",
"import_button": "Inventar importieren",
"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."
},
"import_export_sub": "Importieren und exportieren Sie Ihren Lagerbestand in und aus einer CSV-Datei",
"reports": "Berichte",
"reports_set": {
"asset_labels": "Asset-ID-Etiketten",
"asset_labels_button": "Etiketten-Generator",
"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.",
"bill_of_materials": "Stückliste",
"bill_of_materials_button": "Stückliste generieren",
"bill_of_materials_sub": "Generiert eine CSV-Datei (Comma Separated Values), die in ein Tabellenkalkulationsprogramm importiert werden kann"
},
"reports_sub": "Erstellen Sie verschiedene Berichte für Ihr Inventar."
}
}

View File

@@ -18,8 +18,8 @@
},
"item": {
"create_modal": {
"title": "Create Item",
"photo_button": "Photo \uD83D\uDCF7"
"photo_button": "Photo 📷",
"title": "Create Item"
},
"view": {
"selectable": {
@@ -38,6 +38,9 @@
"location": {
"create_modal": {
"title": "Create Location"
},
"tree": {
"no_locations": "No locations available. Add new locations through the\n `<`span class=\"link-primary\"`>`Create`<`/span`>` button on the navigation bar."
}
}
},
@@ -101,6 +104,75 @@
"tips_sub": "Search Tips",
"updated_at": "Updated At"
},
"labels": {
"no_results": "No Labels Found"
},
"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)"
},
"locations": {
"no_results": "No Locations Found"
},
"maintenance": {
"filter": {
"both": "Both",
"completed": "Completed",
"scheduled": "Scheduled"
},
"list": {
"create_first": "Create Your First Entry",
"delete": "Delete",
"edit": "Edit",
"new": "New",
"complete": "Complete",
"duplicate" : "Duplicate"
},
"modal": {
"completed_date": "Completed Date",
"cost": "Cost",
"delete_confirmation": "Are you sure you want to delete this entry?",
"edit_action": "Update",
"edit_title": "Edit Entry",
"entry_name": "Entry Name",
"new_action": "Create",
"new_title": "New Entry",
"notes": "Notes",
"scheduled_date": "Scheduled Date"
},
"monthly_average": "Monthly Average",
"toast": {
"failed_to_create": "Failed to create entry",
"failed_to_delete": "Failed to delete entry",
"failed_to_update": "Failed to update entry"
},
"total_cost": "Total Cost",
"total_entries": "Total Entries"
},
"menu": {
"home": "Home",
"locations": "Locations",
"maintenance": "Maintenance",
"profile": "Profile",
"search": "Search",
"tools": "Tools"
},
"profile": {
"active": "Active",
"change_password": "Change Password",
@@ -108,12 +180,15 @@
"current_password": "Current Password",
"delete_account": "Delete Account",
"delete_account_sub": "Delete your account and all its associated data. This can not be undone.",
"display_header": "{ currentValue, select, true {Hide Header} false {Show Header} other {Not Hit}}",
"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",
"language": "Language",
"new_password": "New Password",
"no_notifiers": "No notifiers configured",
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Notifier",
"notifiers": "Notifiers",
"notifiers_sub": "Get notifications for upcoming maintenance reminders",
@@ -121,8 +196,47 @@
"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",
"update_language": "Update Language",
"url": "URL",
"user_profile": "User Profile",
"user_profile_sub": "Invite users, and manage your account."
},
"tools": {
"actions": "Inventory Actions",
"actions_set": {
"ensure_ids": "Ensure Asset IDs",
"ensure_ids_button": "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_import_refs": "Ensure Import Refs",
"ensure_import_refs_button": "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.",
"set_primary_photo": "Set Primary Photo",
"set_primary_photo_button": "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>'",
"zero_datetimes": "Zero Item Date Times",
"zero_datetimes_button": "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>'"
},
"actions_sub": "Apply Actions to your inventory in bulk. These are irreversible actions. '<b>'Be careful.'</b>'",
"import_export": "Import/Export",
"import_export_set": {
"export": "Export Inventory",
"export_button": "Export Inventory",
"export_sub": "Exports the standard CSV format for Homebox. This will export all items in your inventory.",
"import": "Import Inventory",
"import_button": "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_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.",
"reports": "Reports",
"reports_set": {
"asset_labels": "Asset ID Labels",
"asset_labels_button": "Label Generator",
"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.",
"bill_of_materials": "Bill of Materials",
"bill_of_materials_button": "Generate BOM",
"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."
},
"reports_sub": "Generate different reports for your inventory."
}
}

View File

@@ -18,6 +18,7 @@
},
"item": {
"create_modal": {
"photo_button": "Foto 📷",
"title": "Crear Elemento"
},
"view": {
@@ -44,8 +45,10 @@
"build": "Compilación: { build }",
"confirm": "Confirmar",
"create": "Crear",
"create_and_add": "Crear y Añadir Otro",
"created": "Creado",
"email": "Email",
"follow_dev": "Seguir al Desarrollador",
"github": "Proyecto GitHub",
"items": "Elementos",
"join_discord": "Únete al Discord",
@@ -57,22 +60,20 @@
"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 }"
"version": "Versión: { version }",
"welcome": "Bienvenido/a, { username }"
},
"index": {
"disabled_registration": "Registro Desactivado",
"dont_join_group": "¿No quieres unirte a un grupo?",
"joining_group": "¡Te estás uniendo a un grupo existente!",
"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!"
"tagline": "Rastrea, Organiza y Gestiona tus Cosas."
},
"items": {
"add": "Añadir",
@@ -93,35 +94,96 @@
"query_id": "Consultar Número ID del Activo: { id }",
"reset_search": "Restablecer Búsqueda",
"results": "{ total } Resultados",
"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.",
"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"
},
"languages": {
"ca": "Catalán",
"de": "Alemán",
"en": "Inglés",
"es": "Español",
"fr": "Francés",
"hu": "Húngaro",
"it": "Italiano",
"nl": "Holandés",
"pl": "Polaco",
"pt-BR": "Portugués (Brasil)",
"ru": "Ruso",
"sl": "Esloveno",
"sv": "Sueco",
"tr": "Turco",
"zh-CN": "Chino (Simplificado)",
"zh-HK": "Chino (Hong Kong)",
"zh-MO": "Chino (Macao)",
"zh-TW": "Chino (Tradicional)"
},
"profile": {
"active": "Activo",
"change_password": "Cambiar Contraseña",
"currency_format": "Formato Divisa",
"current_password": "Contraseña Actual",
"delete_account": "Eliminar Cuenta",
"delete_account_sub": "Elimina tu cuenta y todos sus datos asociados. Esto no se puede deshacer.",
"display_header": "{ currentValue, select, true {Ocultar Encabezado} false {Mostrar Encabezado} other {Desconocido}}",
"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",
"language": "Idioma",
"new_password": "Nueva Contraseña",
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Notificación",
"notifiers": "Notificaciones",
"notifiers_sub": "Recibe notificaciones de los próximos recordatorios de mantenimiento",
"test": "Probar",
"theme_settings": "Ajustes de Tema",
"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.",
"update_group": "Actualizar Grupo",
"update_language": "Cambiar Idioma",
"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."
"user_profile_sub": "Invita a usuarios y gestiona tu cuenta."
},
"tools": {
"actions": "Acciones de Inventario",
"actions_set": {
"ensure_ids": "Garantizar IDs de Activos",
"ensure_ids_button": "Garantizar IDs de Activos",
"ensure_ids_sub": "Garantiza que todos los elementos de tu inventario tienen un campo asset_id válido. Esto se hace encontrando el campo asset_id actual más alto en la base de datos y aplicando el siguiente valor a cada artículo que tenga un campo asset_id sin establecer. Esto se hace en orden del campo created_at.",
"ensure_import_refs": "Garantizar Referencias de Importación",
"ensure_import_refs_button": "Garantizar Referencias de Importación",
"ensure_import_refs_sub": "Garantiza que todos los artículos de tu inventario tienen un campo import_ref válido. Esto se hace generando aleatoriamente una cadena de 8 caracteres para cada artículo que tenga un campo import_ref sin establecer.",
"set_primary_photo": "Establecer Foto Principal",
"set_primary_photo_button": "Establecer Foto Principal",
"set_primary_photo_sub": "En la versión v0.10.0 de Homebox, se añadió el indicador de imagen principal a los ficheros adjuntos de tipo foto. Esta acción establecerá la primera imagen de cada artículo como su imagen principal, si no hay una imagen principal ya definida. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/pull/576\">'Ver PR #576 en GitHub'</a>'",
"zero_datetimes": "Cero Horas Elementos",
"zero_datetimes_button": "Cero Horas Elementos",
"zero_datetimes_sub": "Restablece el valor de la hora para todos los campos de fecha/hora en tu inventario al principio de esa fecha. Esto se hace para corregir un error que se introdujo al principio del desarrollo de la aplicación, que causó que el valor de la hora se almacenase con la fecha, lo cual produjo problemas al mostrar valores precisos del campo. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/issues/236\" target=\"_blank\">'Ver el issue #236 de GitHub para más detalles.'</a>'"
},
"actions_sub": "Aplica Acciones a tu inventario de forma masiva. Estas son acciones irreversibles. '<b>'Ten Cuidado.'</b>'",
"import_export": "Importar/Exportar",
"import_export_set": {
"export": "Exportar Inventario",
"export_button": "Exportar Inventario",
"export_sub": "Exporta el formato CSV estándar para Homebox. Esto exportará todos los elementos de tu inventario.",
"import": "Importar Inventario",
"import_button": "Importar Inventario",
"import_sub": "Importa el formato CSV estándar para Homebox. Sin una columna '<code>'HB.import_ref'</code>', esto '<b>'no'</b>' sobrescribirá cualquier elemento existente en tu inventario, sólo añadirá nuevos artículos. Las filas con una columna '<code>'HB.import_ref'</code>' se fusionan con los artículos existentes con la misma import_ref, si existe."
},
"import_export_sub": "Importa y exporta tu inventario a y desde un archivo CSV. Esto es útil para migrar tu inventario a una nueva instancia de Homebox.",
"reports": "Informes",
"reports_set": {
"asset_labels": "Etiquetas de ID de Activo",
"asset_labels_button": "Generador de Etiquetas",
"asset_labels_sub": "Genera un PDF para impresión de etiquetas para un rango de ID de Activos. Estas etiquetas no son específicas de tu inventario, por lo que puedes imprimirlas con antelación y aplicarlas a tu inventario cuando las recibas.",
"bill_of_materials": "Lista de Materiales",
"bill_of_materials_button": "Generar lista de materiales",
"bill_of_materials_sub": "Genera un archivo CSV (Valores Separados por Comas) que puede importarse a un programa de hojas de cálculo. Es un resumen de tu inventario con información básica sobre artículos y precios."
},
"reports_sub": "Genera diferentes informes para tu inventario."
}
}

View File

@@ -1,107 +1,86 @@
{
"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": {
"app": {
"import_dialog": {
"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.",
"description": "Importer un fichier CSV contenant vos articles, étiquettes, et emplacements. Voir la documentation pour plus dinformations sur le format requis.",
"title": "Importer un fichier CSV",
"upload": "Téléverser"
}
},
"global": {
"page_qr_code": {
"page_url": "URL de la page"
},
"password_score": {
"password_strength": "Force du mot de passe"
}
},
"item": {
"create_modal": {
"photo_button": "Photo 📷",
"title": "Créer un article"
},
"view": {
"selectable": {
"card": "Carte",
"items": "Articles",
"no_items": "Pas d'articles à afficher",
"table": "Tableau"
}
}
},
"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"
},
"tree": {
"no_locations": "Aucun emplacement disponible. Ajoutez votre premiers emplacements avec le bouton `<`span class=\"link-primary\"`>`Créer`<`/span`>` dans la barre de navigation."
}
}
},
"global": {
"password_score": {
"password_strength": "Solidité du mot de passe"
"build": "Assemblage : { build }",
"confirm": "Confirmer",
"create": "Créer",
"create_and_add": "Créer et en ajouter un autre",
"created": "Créé",
"email": "Courriel",
"follow_dev": "Suivre le développeur",
"github": "Projet GitHub",
"items": "Articles",
"join_discord": "Rejoindre le Discord",
"labels": "Étiquettes",
"locations": "Emplacements",
"name": "Nom",
"password": "Mot de passe",
"read_docs": "Lire la documentation",
"search": "Rechercher",
"sign_out": "Déconnexion",
"submit": "Soumettre",
"version": "Version : { version }",
"welcome": "Bienvenue, { username }"
},
"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": ""
"index": {
"disabled_registration": "Les inscriptions sont désactivées",
"dont_join_group": "Vous ne voulez pas joindre un groupe ?",
"joining_group": "Vous rejoignez un groupe existant!",
"login": "Se connecter",
"register": "S'enregistrer",
"remember_me": "Se souvenir de moi",
"set_email": "Quel est vôtre courriel?",
"set_name": "Quel est votre nom?",
"set_password": "Définir votre mot de passe",
"tagline": "Répertoriez, organisez et gérez vos affaires."
},
"items": {
"add": "Ajouter",
"created_at": "",
"created_at": "Créé à",
"custom_fields": "Champs personnalisés",
"field_selector": "Sélecteur de champ",
"field_value": "Valeur du champ",
@@ -115,14 +94,148 @@
"order_by": "Trier par",
"pages": "Page { page } sur { totalPages }",
"prev_page": "Page précédente",
"query_id": "",
"query_id": "Interrogation du numéro d'identification de l'actif : { id }",
"reset_search": "Réinitialiser la recherche",
"results": "{ total } résultats",
"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": "",
"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": "",
"results": "{ total } résultats"
"updated_at": "Mis à jour le"
},
"labels": {
"no_results": "Aucune étiquette trouvée"
},
"languages": {
"ca": "Catalan",
"de": "Allemand",
"en": "Anglais",
"es": "Espagnol",
"fr": "Français",
"hu": "Hongrois",
"it": "Italien",
"nl": "Néerlandais",
"pl": "Polonais",
"pt-BR": "Portugais (Brésil)",
"ru": "Russe",
"sl": "Slovène",
"sv": "Suédois",
"tr": "Turc",
"zh-CN": "Chinois (simplifié)",
"zh-HK": "Chinois (Hong Kong)",
"zh-MO": "Chinois (Macao)",
"zh-TW": "Chinois (traditionnel)"
},
"locations": {
"no_results": "Aucun emplacement trouvé"
},
"maintenance": {
"filter": {
"both": "Toutes",
"completed": "Terminées",
"scheduled": "Prévues"
},
"list": {
"complete": "Terminer",
"create_first": "Créer votre première entrée",
"delete": "Supprimer",
"duplicate": "Dupliquer",
"edit": "Modifier",
"new": "Ajouter"
},
"modal": {
"completed_date": "Date d'achèvement",
"cost": "Coût",
"delete_confirmation": "Êtes-vous certain de vouloir supprimer cette entrée ?",
"edit_action": "Modifier",
"edit_title": "Modifier l'entrée",
"entry_name": "Nom",
"new_action": "Créer",
"new_title": "Nouvelle entrée",
"notes": "Notes",
"scheduled_date": "Date prévue"
},
"monthly_average": "Moyenne mensuelle",
"toast": {
"failed_to_create": "Échec de suppression de l'entrée",
"failed_to_delete": "Échec de création de l'entrée",
"failed_to_update": "Échec de mise à jour de l'entrée"
},
"total_cost": "Coût total",
"total_entries": "Nombre d'entrées"
},
"menu": {
"home": "Accueil",
"locations": "Emplacements",
"maintenance": "Maintenance",
"profile": "Profil",
"search": "Recherche",
"tools": "Outils"
},
"profile": {
"active": "Actif",
"change_password": "Changer de mot de passe",
"currency_format": "Format de la devise",
"current_password": "Mot de passe actuel",
"delete_account": "Supprimer le compte",
"delete_account_sub": "Supprimer le compte et toutes ses données. Aucune récupération possible.",
"display_header": "{ currentValue, select, true {Masquer len-tête} false {Afficher len-tête} other {Not Hit}}",
"enabled": "Activé",
"gen_invite": "Générer un lien dinvitation",
"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.",
"inactive": "Inactif",
"language": "Langue",
"new_password": "Nouveau mot de passe",
"no_notifiers": "Aucune notification configurée",
"notifier_modal": "Notifications { type, select, true {Edit} false {Create} other {Other}}",
"notifiers": "Notifications",
"notifiers_sub": "Recevez des notifications pour vous prévenir des prochaines maintenances.",
"test": "Test",
"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.",
"update_group": "Mettre à jour le groupe",
"update_language": "Mettre à jour la langue",
"url": "URL",
"user_profile": "Profil utilisateur",
"user_profile_sub": "Gérez votre compte et invitez des utilisateurs."
},
"tools": {
"actions": "Actions dinventaire",
"actions_set": {
"ensure_ids": "Vérifier les identifiants d'actif",
"ensure_ids_button": "Vérifier les identifiants d'actifs",
"ensure_ids_sub": "Vérifie que tous les éléments de votre inventaire comportent un champ asset_id valide. Ceci est effectué en cherchant lidentifiant actuel le plus élevé et en appliquant les valeurs suivantes à tous les éléments sans identifiant. L'action est effectuée en suivant l'ordre du champ created_at.",
"ensure_import_refs": "Vérifier les références d'importation",
"ensure_import_refs_button": "Vérifier les références d'importation",
"ensure_import_refs_sub": "Vérifie que tous les éléments de votre inventaire comportent un champ import_ref valide. Ceci est effectué en générant une chaîne de 8 caractères aléatoires pour chaque élément ne comportant pas de champ import_ref.",
"set_primary_photo": "Définir la photo principale",
"set_primary_photo_button": "Définir la photo principale",
"set_primary_photo_sub": "Dans la version v0.10.0 d'Homebox, le champ d'image principal à été ajouté aux pièces jointes de type photo. Cette action définira en tant qu'image principale toute première image en pièce jointe déjà présente en base de donnée, si le champ n'est pas déjà défini. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/pull/576\">'Voir GitHub PR #576'</a>'",
"zero_datetimes": "Remettre à zéro les dates et heures",
"zero_datetimes_button": "Remettre à zéro les dates et heures"
},
"actions_sub": "Appliquer des actions en masse à votre inventaire. Ces actions sont irréversibles. '<b>'Soyez vigilant.'</b>'",
"import_export": "Importer/Exporter",
"import_export_set": {
"export": "Exporter l'inventaire",
"export_button": "Exporter l'inventaire",
"export_sub": "Exporte l'inventaire au format CSV Homebox. Cela exportera l'intégralité de votre inventaire.",
"import": "Importer l'inventaire",
"import_button": "Importer l'inventaire",
"import_sub": "Importer un fichier CSV au format Homebox. Sans la colonne '<code>'HB.import_ref'</code>' , cela '<b>'n'écrasera pas'</b>' d'éléments de votre inventaire actuel, seuls les nouveaux éléments seront importés. Les lignes contenant une colonne '<code>'HB.import_ref'</code>' seront fusionnées avec les éléments existants comportant le même '<code>'HB.import_ref'</code>', si existant."
},
"import_export_sub": "Importez et exportez votre inventaire vers et depuis un fichier CSV",
"reports": "Rapports",
"reports_set": {
"asset_labels": "Étiquettes didentifiant dactif",
"asset_labels_button": "Générateur détiquettes",
"asset_labels_sub": "Génère un fichier PDF imprimable pour une plage d'identifiants d'actifs. Ils ne sont pas spécifiques à votre inventaire donc vous pouvez les imprimer en avance et les appliquer à votre inventaire par la suite.",
"bill_of_materials": "Nomenclature",
"bill_of_materials_button": "Générer une nomenclature",
"bill_of_materials_sub": "Génère un fichier CSV (valeurs séparées par des virgules) qui peut être importé dans un tableur. Il s'agit d'un aperçu de votre inventaire contenant les informations essentielles."
},
"reports_sub": "Générez différents rapports pour votre inventaire."
}
}

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

@@ -0,0 +1,242 @@
{
"components": {
"app": {
"import_dialog": {
"change_warning": "A meglévő import_ref-fel rendelkező tételek importálásának menete megváltozott. Ha a CSV fájlban van import_ref, \nakkor a tételt felülírják a CSV fájlban található értékek.",
"description": "Importálj egy CSV fájlt, amely tartalmazza a tételeidet, címkéidet és helyeidet. A szükséges formátumról bővebben \na dokumentációban olvashatsz.",
"title": "Importálás CSV-fájlból",
"upload": "Feltöltés"
}
},
"global": {
"page_qr_code": {
"page_url": "Oldal URL-je"
},
"password_score": {
"password_strength": "Jelszó erőssége"
}
},
"item": {
"create_modal": {
"photo_button": "Fénykép 📷",
"title": "Új elem létrehozása"
},
"view": {
"selectable": {
"card": "Kártya",
"items": "Tételek",
"no_items": "Nincs megjeleníthető elem",
"table": "Táblázat"
}
}
},
"label": {
"create_modal": {
"title": "Címke létrehozása"
}
},
"location": {
"create_modal": {
"title": "Új hely létrehozása"
},
"tree": {
"no_locations": "Nincs elérhető hely. Adj hozzá új helyet a\n `<`span class=\"link-primary\"`>`Létrehozás`<`/span`>` gombbal a navigációs sávon."
}
}
},
"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": "Kövesd a fejlesztőt",
"github": "Github projekt",
"items": "Tételek",
"join_discord": "Csatlakozz a Discordhoz",
"labels": "Címkék",
"locations": "Helyek",
"name": "Név",
"password": "Jelszó",
"read_docs": "Olvasd el a dokumentációt",
"search": "Keresés",
"sign_out": "Kijelentkezés",
"submit": "Elküldés",
"version": "Verzió: { version }",
"welcome": "Üdv, { username }"
},
"index": {
"disabled_registration": "Regisztráció kikapcsolva",
"dont_join_group": "Nem szeretnél csatlakozni egy csoporthoz?",
"joining_group": "Meglévő csoporthoz csatlakozol!",
"login": "Bejelentkezés",
"register": "Regisztráció",
"remember_me": "Emlékezzen rám!",
"set_email": "Mi az email címed?",
"set_name": "Mi a neved?",
"set_password": "Állíts be egy jelszót!",
"tagline": "Kövesd nyomon, rendszerezd és kezeld a dolgaidat."
},
"items": {
"add": "Hozzáadás",
"created_at": "Létrehozás dátuma",
"custom_fields": "Egyedi mezők",
"field_selector": "Mezőválasztó",
"field_value": "Mező értéke",
"first": "Első",
"include_archive": "Archivált elemek belefoglalása",
"last": "Utolsó",
"negate_labels": "Címkeválasztás negálása",
"next_page": "Következő oldal",
"no_results": "Egy elem sem található",
"options": "Opciók",
"order_by": "Rendezés",
"pages": "{page}/{totalPages} oldal",
"prev_page": "Előző oldal",
"query_id": "Eszközazonosító szám lekérdezése: { id }",
"reset_search": "Alaphelyzet",
"results": "{total} találat",
"tip_1": "A hely- és címkeszűrők a „vagy” műveletet használják. Ha egynél többet választasz ki,\n bármelyik egyezése esetén megjelenik a tétel.",
"tip_2": "A '#' előtaggal ellátott keresések egy eszközazonosítót fognak lekérdezni (például '#000-001')",
"tip_3": "A mezőszűrők a „vagy” műveletet használják. Ha egynél többet választasz ki,\n bármelyik egyezése esetén megjelenik a tétel.",
"tips": "Tippek",
"tips_sub": "Tippek a kereséshez",
"updated_at": "Változtatás dátuma"
},
"labels": {
"no_results": "Nem található címke"
},
"languages": {
"ca": "Katalán",
"de": "Német",
"en": "Angol",
"es": "Spanyol",
"fr": "Francia",
"hu": "Magyar",
"it": "Olasz",
"nl": "Holland",
"pl": "Lengyel",
"pt-BR": "Portugál (brazíliai)",
"ru": "Orosz",
"sl": "Szlovén",
"sv": "Svéd",
"tr": "Török",
"zh-CN": "Kínai (egyszerűsített)",
"zh-HK": "Kínai (hongkongi)",
"zh-MO": "Kínai (makaói)",
"zh-TW": "Kínai (hagyományos)"
},
"locations": {
"no_results": "Nem található hely"
},
"maintenance": {
"filter": {
"both": "Mindkettő",
"completed": "Teljesítve",
"scheduled": "Időzítve"
},
"list": {
"complete": "Kész",
"create_first": "Hozd létre az első bejegyzést",
"delete": "Törlés",
"duplicate": "Másolás",
"edit": "Szerkesztés",
"new": "Új"
},
"modal": {
"completed_date": "Teljesítés dátuma",
"cost": "Költség",
"delete_confirmation": "Biztosan törli ezt a bejegyzést?",
"edit_action": "Módosítás",
"edit_title": "Bejegyzés szerkesztése",
"entry_name": "Bejegyzés neve",
"new_action": "Létrehozás",
"new_title": "Új bejegyzés",
"notes": "Megjegyzések",
"scheduled_date": "Ütemezett dátum"
},
"monthly_average": "Havi átlag",
"toast": {
"failed_to_create": "Bejegyzés létrehozása sikertelen",
"failed_to_delete": "Bejegyzés törlése sikertelen",
"failed_to_update": "Bejegyzés frissítése sikertelen"
},
"total_cost": "Teljes költség",
"total_entries": "Összes bejegyzés"
},
"menu": {
"home": "Kezdőlap",
"locations": "Helyek",
"maintenance": "Karbantartás",
"profile": "Profil",
"search": "Keresés",
"tools": "Eszközök"
},
"profile": {
"active": "Aktív",
"change_password": "Jelszó módosítása",
"currency_format": "Pénz formátum",
"current_password": "Jelenlegi jelszó",
"delete_account": "Fiók törlése",
"delete_account_sub": "Törlöd a fiókodat és az összes kapcsolódó adatot. Ezt a műveletet nem lehet visszavonni.",
"display_header": "{ currentValue, select, true {Fejléc elrejtése} false {Fejléc megjelenítése} other{Nincs találat}}",
"enabled": "Engedélyezve",
"gen_invite": "Meghívó link létrehozása",
"group_settings": "Csoport beállításai",
"group_settings_sub": "Ezek a csoport közös beállításai. Lehet hogy frissítened kell az oldalt a böngésződben a beállítások megváltoztatása után.",
"inactive": "Inaktív",
"language": "Nyelv",
"new_password": "Új jelszó",
"no_notifiers": "Nincs értesítő beállítva",
"notifier_modal": "Értesítő { type, select, true {szerkesztése} false {létrehozása} other {egyéb}}",
"notifiers": "Értesítők",
"notifiers_sub": "Értesítések kérése a közelgő karbantartási emlékeztetőkről",
"test": "Teszt",
"theme_settings": "Téma Beállítások",
"theme_settings_sub": "A témabeállítások a böngésző helyi tárhelyén tárolódnak. Bármikor megváltoztathatod a témát. Ha problémába\n ütközöl a téma beállításakor, próbáld meg frissíteni az oldalt a böngésződben.",
"update_group": "Csoport módosítása",
"update_language": "Nyelv átállítása",
"url": "URL",
"user_profile": "Felhasználói profil",
"user_profile_sub": "Hívj meg felhasználókat, és kezeld a fiókodat."
},
"tools": {
"actions": "Készletműveletek",
"actions_set": {
"ensure_ids": "Eszközazonosítók meglétének biztosítása",
"ensure_ids_button": "Eszközazonosítók generálása",
"ensure_ids_sub": "Biztosítja, hogy a készletben lévő összes tétel rendelkezzen érvényes asset_id (eszközazonosító) mezővel. Ehhez megkeresi a legmagasabb asset_id mezőértéket az adatbázisban és minden olyan tételhez, amelynek nem beállított az asset_id mezője, rendre eggyel növelt értéket állít be. Ezt a created_at mezők értékének (a tétel létrehozásának dátuma) sorrendjében teszi.",
"ensure_import_refs": "Importálási hivatkozások meglétének biztosítása",
"ensure_import_refs_button": "Hivatkozások generálása",
"ensure_import_refs_sub": "Biztosítja, hogy a készletben lévő összes tétel rendelkezzen érvényes import_ref mezővel. Véletlenszerűen generál egy 8 hosszúságú karakterláncot minden olyan tételhez, amelynél az import_ref mező üres.",
"set_primary_photo": "Elsődleges fénykép hozzárendelése",
"set_primary_photo_button": "Hozzárendelés",
"set_primary_photo_sub": "A Homebox v0.10.0 verziójában hozzáadtuk a fénykép típusú mellékletekhez az elsődleges fényképként történő megjelölés lehetőségét. Ezzel a művelettel a mellékletekben található első fényképet állítod be elsődleges fényképnek, ha ilyen a tételhez még nincs kiválasztva. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/pull/576\">'Lásd az #576 GitHub PR-t'</a>'",
"zero_datetimes": "Idő törlése a tételek dátummezőiből",
"zero_datetimes_button": "Dátummezők javítása",
"zero_datetimes_sub": "Visszaállítja a dátumot és időt tartalmazó mezők értékét a dátum kezdetére a teljes készletben. Ezzel javíthatsz egy olyan bugot, mely során az oldal fejlesztésének korai szakaszában az időértékek mentése a dátumok pontos megjelenítésében hibát okozott. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/issues/236\" target=\"_blank\">'Lásd a #236 Github Issue-t további részletekért.'</a>'"
},
"actions_sub": "Műveletek tömeges alkalmazása a készletre. Ezeket a vissza nem vonható műveleteket csak '<b>'kellő körültekintés mellett használd'</b>'.",
"import_export": "Import/Export",
"import_export_set": {
"export": "Készlet exportálása",
"export_button": "Készlet exportálása",
"export_sub": "Exportálja a Homebox szabványos CSV formátumát. Ez minden készletedben található tételt exportál.",
"import": "Készlet importálása",
"import_button": "Készlet importálása",
"import_sub": "Importálja a Homebox szabványos CSV formátumát. Amennyiben nem található '<code>'HB.import_ref'</code>' oszlop a fájlban, ez '<b>'nem'</b>' ír felül létező tételeket a készletedben, csak újakat ad hozzá. Azon sorok, melyeknél a '<code>'HB.import_ref'</code>' oszlop értéke megegyezik egy létező tétel import_ref mezőjének értékével, a sor tartalma beolvad a létező tételbe."
},
"import_export_sub": "Készlet importálása és exportálása CSV-fájlba és CSV-fájlból. Ez hasznos lehet a készleted átmozgatásához a Homebox egy új példányába.",
"reports": "Jelentések",
"reports_set": {
"asset_labels": "Eszközazonosító címkék",
"asset_labels_button": "Címkegenerátor",
"asset_labels_sub": "Nyomtatható PDF-fájlt hoz létre, amely címkéket tartalmaz eszközazonosítók egy meghatározott tartományához. Ezek nem készletspecifikusak, így előre is kinyomtathatod a címkéket, és később felviheted a készleted beérkező tételeire.",
"bill_of_materials": "Anyagjegyzék",
"bill_of_materials_button": "Jegyzék létrehozása",
"bill_of_materials_sub": "Létrehoz egy CSV (vesszővel elválasztott értékek) fájlt, amely importálható egy táblázatkezelő programba. Ez a készleted összesítése a tételek alap és árra vonatkozó információival."
},
"reports_sub": "Hozz létre különböző jelentéseket a készletedhez."
}
}

View File

@@ -18,6 +18,7 @@
},
"item": {
"create_modal": {
"photo_button": "Foto 📷",
"title": "Crea Oggetto"
},
"view": {
@@ -41,7 +42,7 @@
}
},
"global": {
"build": "Build: { build }",
"build": "Compila: { build }",
"confirm": "Conferma",
"create": "Crea",
"create_and_add": "Crea e aggiungi un altro",
@@ -100,6 +101,26 @@
"tips_sub": "Suggerimenti per la Ricerca",
"updated_at": "Aggiornato Il"
},
"languages": {
"ca": "Catalano",
"de": "Tedesco",
"en": "Inglese",
"es": "Spagnolo",
"fr": "Francese",
"hu": "Ungherese",
"it": "Italiano",
"nl": "Olandese",
"pl": "Polacco",
"pt-BR": "Portoghese (Brasile)",
"ru": "Russo",
"sl": "Sloveno",
"sv": "Svedese",
"tr": "Turco",
"zh-CN": "Cinese (semplificato)",
"zh-HK": "Cinese Mandarino",
"zh-MO": "Cinese (Macao)",
"zh-TW": "Cinese (tradizionale)"
},
"profile": {
"active": "Attivo",
"change_password": "Cambia Password",
@@ -107,11 +128,13 @@
"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.",
"display_header": "{ currentValue, select, true {Nascondi intestazione} false {Mostra intestazione} other {Sconosciuto}}",
"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",
"language": "Lingua",
"new_password": "Nuova Password",
"notifier_modal": "{ type, select, true {Modifica} false {Crea} other {Altro}} Notifier",
"notifiers": "Notifiche",
@@ -120,8 +143,47 @@
"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",
"update_language": "Aggiorna Lingua",
"url": "URL",
"user_profile": "Profilo Utente",
"user_profile_sub": "Invita utenti e gestisci il tuo account."
},
"tools": {
"actions": "Azioni Inventario",
"actions_set": {
"ensure_ids": "Verifica ID delle risorse",
"ensure_ids_button": "Verifica ID delle risorse",
"ensure_ids_sub": "Garantisce che tutti gli articoli nel tuo inventario abbiano un campo asset_id valido. Questo viene fatto trovando il campo asset_id corrente più alto nel database e applicando il valore successivo a ogni elemento che ha un campo asset_id non impostato. Questo viene fatto per il campo created_at.",
"ensure_import_refs": "Verifica riferimenti di importazione",
"ensure_import_refs_button": "Verifica riferimenti di importazione",
"ensure_import_refs_sub": "Verifica che tutti gli articoli nel tuo inventario abbiano un campo import_ref valido. Questo viene fatto generando in modo casuale una stringa di 8 caratteri per ogni elemento che ha un campo import_ref non impostato.",
"set_primary_photo": "Imposta foto principale",
"set_primary_photo_button": "Imposta immagine principale",
"set_primary_photo_sub": "Nella versione v0.10.0 di Homebox, il campo immagine principale è stato aggiunto agli allegati di tipo foto. Questa azione imposterà il campo immagine principale alla prima immagine nella matrice allegati nel database, se non è già impostato. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/pull/576\">'Vedi GitHub PR #576'</a>'",
"zero_datetimes": "Azzera Data e Orario oggetti",
"zero_datetimes_button": "Azzera Date e Ora articoli",
"zero_datetimes_sub": "Reimposta il valore dell'ora per tutti i campi data e ora dell'inventario all'inizio della data. Questo è per correggere un bug che è stato introdotto all'inizio dello sviluppo del sito che ha causato il valore di orario memorizzato con il tempo che ha causato problemi con i campi data visualizzazione dei valori esatti. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/issues/236\" target=\"_blank\">'Vedi Github Issue #236 per maggiori dettagli.'</a>'"
},
"actions_sub": "Applica Azioni massive al tuo inventario. Questo sono azioni irreversibili. '<b>'Presta attenzione.'</b>'",
"import_export": "Importa/Esporta",
"import_export_set": {
"export": "Esporta Inventario",
"export_button": "Esporta Inventario",
"export_sub": "Esporta il formato CSV standard per Homebox. Questo esporterà tutti gli oggetti del tuo inventario.",
"import": "Importa Inventario",
"import_button": "Importa Inventario",
"import_sub": "Importa il formato CSV standard per Homebox. Senza una colonna '<code>'HB.import_ref'</code>' questo '<b>'non'</b>' sovrascriverà gli oggetti esistenti nel tuo inventario, aggiungerà solamente nuovi oggetti. Le righe con una colonna '<code>'HB.import_ref'</code>' saranno unite agli oggetti esistenti con lo stesso import_ref, se presente."
},
"import_export_sub": "Importa ed esporta il tuo inventario da e verso un file CSV. Questo è utile per migrare il tuo inventario verso una nuova istanza di Homebox.",
"reports": "Rapporti",
"reports_set": {
"asset_labels": "Etichette ID risorsa",
"asset_labels_button": "Generatore di etichette",
"asset_labels_sub": "Genera una etichetta PDF stampabile per un gruppo di ID Risorsa. Le etichette non sono specifiche per il tuo inventario così puoi stamparle in anticipo ed applicarle al tuo inventario quando lo ricevi.",
"bill_of_materials": "Distinta base",
"bill_of_materials_button": "Genera BOM",
"bill_of_materials_sub": "Genera un file CSV (Comma Separated Values) che può essere importato in un foglio di calcolo. Questo è un sommario del tuo inventario con informazioni di base su oggetto e prezzo."
},
"reports_sub": "Genera diversi report per il tuo inventario."
}
}

View File

@@ -1,66 +1,32 @@
{
"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."
"description": "Importeer een CSV bestand met je objecten, labels en locaties. Zie documentatie voor meer informatie m.b.t.\n vereist format.",
"title": "Importeer CSV bestand",
"upload": "Upload"
}
},
"global": {
"page_qr_code": {
"page_url": "Pagina URL"
},
"password_score": {
"password_strength": "Wachtwoord sterkte"
}
},
"item": {
"create_modal": {
"title": "Maak object",
"photo_button": "Foto 📷"
"photo_button": "Foto 📷",
"title": "Maak object"
},
"view": {
"selectable": {
"items": "Objecten",
"card": "Kaart",
"table": "Tabel",
"no_items": "Geen objecten om te tonen"
"items": "Objecten",
"no_items": "Geen objecten om te tonen",
"table": "Tabel"
}
}
},
@@ -75,45 +41,41 @@
}
}
},
"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": "",
"delete_account": "Verwijder account",
"delete_account_sub": "Verwijder je account en alle geassocieerde data. Deze actie kan niet ongedaan worden."
"global": {
"build": "Bouw: { build }",
"confirm": "Bevestigen",
"create": "Maken",
"create_and_add": "Maak en voeg nog een toe",
"created": "Gemaakt",
"email": "E-mail",
"follow_dev": "Volg de ontwikkelaar",
"github": "GitHub Project",
"items": "artikelen",
"join_discord": "Sluit je aan bij de Discord",
"labels": "etiketten",
"locations": "Locaties",
"name": "Naam",
"password": "Wachtwoord",
"read_docs": "Lees de documentatie",
"search": "Zoeken",
"sign_out": "Log uit",
"submit": "Indienen",
"version": "Versie: { version }",
"welcome": "Welkom, { username }"
},
"index": {
"disabled_registration": "Registratie uitgeschakeld",
"dont_join_group": "Wil je niet aan een groep deelnemen?",
"joining_group": "Je neemt deel aan een bestaande groep!",
"login": "Log in",
"register": "Registreer",
"remember_me": "Onthoud mij",
"set_email": "Wat is je mailadres?",
"set_name": "Wat is je naam?",
"set_password": "Stel je wachtwoord in",
"tagline": "Volg, Organiseer en beheer je dingen."
},
"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": "",
"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",
@@ -121,8 +83,151 @@
"field_value": "Veldwaarde",
"first": "Eerst",
"include_archive": "Inclusief gearchiveerde items",
"last": "Laatste",
"last": "Achternaam",
"negate_labels": "Negeer Geselecteerde Etiketten",
"next_page": "Volgende pagina"
"next_page": "Volgende pagina",
"no_results": "Geen Items Gevonden",
"options": "Opties",
"order_by": "Sorteren op",
"pages": "Pagina { page } van { totalPages }",
"prev_page": "Vorige pagina",
"query_id": "ID-nummer van object opvragen: { id }",
"reset_search": "Reset Zoeken",
"results": "{ total } Resultaten",
"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": "Tips",
"tips_sub": "Zoektips",
"updated_at": "Bijgewerkt op"
},
"labels": {
"no_results": "Geen labels gevonden"
},
"languages": {
"ca": "Catalaans",
"de": "Duits",
"en": "Engels",
"es": "Spaans",
"fr": "Frans",
"hu": "Hongaars",
"it": "Italiaans",
"nl": "Nederlands",
"pl": "Pools",
"pt-BR": "Portugees (Brazilië)",
"ru": "Russisch",
"sl": "Sloveens",
"sv": "Zweeds",
"tr": "Turks",
"zh-CN": "Chinees (vereenvoudigd)",
"zh-HK": "Chinees (Hong Kong)",
"zh-MO": "Chinees (Macau)",
"zh-TW": "Chinees (traditioneel)"
},
"locations": {
"no_results": "Geen locaties gevonden"
},
"maintenance": {
"filter": {
"both": "Beide",
"completed": "Voltooid",
"scheduled": "Gepland"
},
"list": {
"complete": "Compleet",
"create_first": "Maak je eerste invoer aan",
"delete": "Verwijderen",
"duplicate": "Dubbel",
"edit": "Bewerken",
"new": "Nieuw"
},
"modal": {
"completed_date": "Voltooiings Datum",
"cost": "Kosten",
"delete_confirmation": "Weet u zeker dat u deze gegevens wilt verwijderen?",
"edit_action": "Bijwerken",
"edit_title": "Bewerk entry",
"entry_name": "Ingangsnaam",
"new_action": "Maak",
"new_title": "Nieuw fragment",
"notes": "Opmerkingen",
"scheduled_date": "Gepland datum"
},
"monthly_average": "Maandelijks",
"toast": {
"failed_to_create": "Kan invoer niet maken",
"failed_to_delete": "Kon item niet verwijderen.",
"failed_to_update": "Kan invoer niet bijwerken"
},
"total_cost": "Totale kosten",
"total_entries": "Totaal aantal Inzendingen"
},
"menu": {
"home": "Home",
"locations": "Locaties",
"maintenance": "Onderhoud",
"profile": "Profiel",
"search": "Zoeken",
"tools": "Gereedschappen"
},
"profile": {
"active": "Actief",
"change_password": "Verander Wachtwoord",
"currency_format": "Valutanotatie",
"current_password": "Huidig Wachtwoord",
"delete_account": "Verwijder account",
"delete_account_sub": "Verwijder je account en alle geassocieerde data. Deze actie kan niet ongedaan worden.",
"enabled": "ingeschakeld",
"gen_invite": "Genereer Uitnodigingslink",
"group_settings": "Groeps Instellingen",
"group_settings_sub": "Gedeelde groepsinstellingen",
"inactive": "Inactief",
"language": "Taal",
"new_password": "Nieuw Wachtwoord",
"no_notifiers": "Geen melders geconfigureerd",
"notifier_modal": "{ type, select, true {Bewerk} false {Creëer} other {overig}} Notifier",
"notifiers": "Notificatie",
"notifiers_sub": "Krijg notificaties voor opkomende onderhouds herinneringen",
"test": "Test",
"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.",
"update_group": "Groep bijwerken",
"update_language": "Taal bijwerken",
"url": "URL",
"user_profile": "Gebruikers Profiel",
"user_profile_sub": "Nodig gebruikers uit, en beheer je account."
},
"tools": {
"actions": "Acties inventariseren",
"actions_set": {
"ensure_ids": "Zorg voor item-ID's",
"ensure_ids_button": "Zorg voor item-ID's",
"ensure_import_refs": "Zorg ervoor dat Import Refs",
"ensure_import_refs_button": "Zorg ervoor dat Import Refs",
"set_primary_photo": "Hoofdfoto instellen",
"set_primary_photo_button": "Hoofdfoto instellen",
"zero_datetimes": "Nul item Datum Tijden",
"zero_datetimes_button": "Nul item Datum Tijden"
},
"actions_sub": "Acties bulksgewijs toepassen op je voorraad. Deze zijn onomkeerbaar '<b>'Wees voorzichtig.'</b>'",
"import_export": "Importeer/Exporteer",
"import_export_set": {
"export": "Export voorraad",
"export_button": "Export voorraad",
"export_sub": "Exporteert het standaard CSV-formaat voor Homebox",
"import": "Inventaris Importeren",
"import_button": "Inventaris Importeren"
},
"import_export_sub": "Importeer en exporteer je voorraad van en naar een CSV-bestand. Dit is handig voor het migreren van je voorraad naar een nieuwe HomeBox installatie.",
"reports": "Rapportages",
"reports_set": {
"asset_labels": "Labels voor item-ID",
"asset_labels_button": "Etiket Generator",
"asset_labels_sub": "Genereert een afdrukbare PDF met labels voor een reeks Asset ID's. Deze zijn niet specifiek voor de voorraad dus kun je deze van te voren printen en gebruiken wanneer je voorraad ontvangt.",
"bill_of_materials": "Materiaallijst",
"bill_of_materials_button": "BOM genereren",
"bill_of_materials_sub": "Genereert een CSV-bestand (door komma's gescheiden waarden) dat kan worden geïmporteerd in een spreadsheetprogramma. Dit is een samenvattting van je voorraad met basis item en prijs informatie."
},
"reports_sub": "Genereer verschillende rapporten van je voorraad."
}
}

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."
}
}

242
frontend/locales/pt-BR.json Normal file
View File

@@ -0,0 +1,242 @@
{
"components": {
"app": {
"import_dialog": {
"change_warning": "O comportamento de importações com import_refs existentes foi alterado. Se houver um import_ref presente no arquivo CSV, o\nitem será atualizado com os valores presentes no arquivo CSV.",
"description": "Importe um arquivo CSV contendo seus items, etiquetas e locais. Consulte a documentação para mais informações\nsobre a formatação aceita.",
"title": "Importar arquivo CSV",
"upload": "Enviar"
}
},
"global": {
"page_qr_code": {
"page_url": "URL da página"
},
"password_score": {
"password_strength": "Segurança da Senha"
}
},
"item": {
"create_modal": {
"photo_button": "Foto📷",
"title": "Criar Item"
},
"view": {
"selectable": {
"card": "Cartão",
"items": "Items",
"no_items": "Nenhum item para exibir",
"table": "Tabela"
}
}
},
"label": {
"create_modal": {
"title": "Criar etiqueta"
}
},
"location": {
"create_modal": {
"title": "Criar Local"
},
"tree": {
"no_locations": "Não há locais disponíveis. Adicione novos locais\n através do botão \"Criar\" na barra de navegação."
}
}
},
"global": {
"build": "Compilação: { build }",
"confirm": "Confirmar",
"create": "Criar",
"create_and_add": "Criar e Adicionar Outro",
"created": "Criado",
"email": "Email",
"follow_dev": "Seguir o desenvolvedor",
"github": "Projeto GitHub",
"items": "Items",
"join_discord": "Junte-se ao Discord",
"labels": "Etiquetas",
"locations": "Locais",
"name": "Nome",
"password": "Senha",
"read_docs": "Leia a Documentação",
"search": "Buscar",
"sign_out": "Sair",
"submit": "Enviar",
"version": "Versão: { version }",
"welcome": "Bem-vindo, {username}"
},
"index": {
"disabled_registration": "Registro Desativado",
"dont_join_group": "Não quer participar de um grupo?",
"joining_group": "Você está se juntando a um grupo existente!",
"login": "Login",
"register": "Registro",
"remember_me": "Lembrar-me",
"set_email": "Qual o seu e-mail?",
"set_name": "Qual é o seu nome?",
"set_password": "Defina a sua Senha",
"tagline": "Rastreie, organize e gerencie suas coisas."
},
"items": {
"add": "Adicionar",
"created_at": "Criado em",
"custom_fields": "Campos Personalizados",
"field_selector": "Seletor de Campo",
"field_value": "Valor do Campo",
"first": "Primeiro",
"include_archive": "Incluir itens arquivados",
"last": "Último",
"negate_labels": "Negar os rótulos selecionados",
"next_page": "Próxima página",
"no_results": "Nenhum Item Encontrado",
"options": "Opções",
"order_by": "Ordenar Por",
"pages": "Página { page } de { totalPages }",
"prev_page": "Página anterior",
"query_id": "Consultando o número de ID do ativo: { id }",
"reset_search": "Reiniciar Pesquisa",
"results": "{ total } Resultados",
"tip_1": "Os filtros de local e etiqueta usam o operador \"OR\". Se você selecionar mais de um, somente um será\nnecessário para obter um resultado.",
"tip_2": "As pesquisas comprefixo '#'' buscam um ID de ativo (exemplo '#000-001')",
"tip_3": "Os filtros de local e etiqueta usam o operador \"OR\". Se você selecionar mais de um, somente um será\nnecessário para obter um resultado.",
"tips": "Dicas",
"tips_sub": "Dicas de pesquisa",
"updated_at": "Atualizado em"
},
"labels": {
"no_results": "Nenhuma etiqueta encontrada"
},
"languages": {
"ca": "Catalão",
"de": "Alemão",
"en": "Inglês",
"es": "Espanhol",
"fr": "Francês",
"hu": "Húngaro",
"it": "Italiano",
"nl": "Holandês",
"pl": "Polonês",
"pt-BR": "Português (Brasil)",
"ru": "Russo",
"sl": "Esloveno",
"sv": "Sueco",
"tr": "Turco",
"zh-CN": "Chinês (Simplificado)",
"zh-HK": "Chinês (Hong Kong)",
"zh-MO": "Chinês (Macau)",
"zh-TW": "Chinês (Tradicional)"
},
"locations": {
"no_results": "Nenhum local encontrado"
},
"maintenance": {
"filter": {
"both": "Ambos",
"completed": "Concluído",
"scheduled": "Agendado"
},
"list": {
"complete": "Completo",
"create_first": "Crie sua primeira entrada",
"delete": "Excluir",
"duplicate": "Duplicar",
"edit": "Editar",
"new": "Novo"
},
"modal": {
"completed_date": "Data de conclusão",
"cost": "Custo",
"delete_confirmation": "Tem certeza que deseja excluir este item?",
"edit_action": "Atualizar",
"edit_title": "Editar entrada",
"entry_name": "Nome de entrada",
"new_action": "Criar",
"new_title": "Nova entrada",
"notes": "Notas",
"scheduled_date": "Data agendada"
},
"monthly_average": "Média Mensal",
"toast": {
"failed_to_create": "Falha ao criar entrada",
"failed_to_delete": "Falha ao excluir entrada",
"failed_to_update": "Falha ao atualizar entrada"
},
"total_cost": "Custo Total",
"total_entries": "Total de Entradas"
},
"menu": {
"home": "Início",
"locations": "Locais",
"maintenance": "Manutenção",
"profile": "Perfil",
"search": "Buscar",
"tools": "Ferramentas"
},
"profile": {
"active": "Ativo",
"change_password": "Alterar Senha",
"currency_format": "Formato da moeda",
"current_password": "Senha Atual",
"delete_account": "Excluir conta",
"delete_account_sub": "Excluir sua conta e todos os dados associados. Essa ação não pode ser desfeita.",
"display_header": "{ currentValue, select, true {Ocultar cabeçalho} false {Mostrar cabeçalho} other {Não encontrado}}",
"enabled": "Ativado",
"gen_invite": "Gerar link de convite",
"group_settings": "Definições do grupo",
"group_settings_sub": "Configurações de Grupo Compartilhado. É possível que tenha que recarregar a página para que alguns ajustes sejam aplicados.",
"inactive": "Inativa",
"language": "Idioma",
"new_password": "Nova Senha",
"no_notifiers": "Nenhum notificador configurado",
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Notificação",
"notifiers": "Notificadores",
"notifiers_sub": "Receba notificações para lembretes de manutenção futuros",
"test": "Teste",
"theme_settings": "Configurações do Tema",
"theme_settings_sub": "As configurações de tema são salvas localmente em seu navegador. Você pode mudar o tema a qualquer momento. Se está\ntendo problemas com a mudança de tema, tente recarregar a pagina.",
"update_group": "Atualizar grupo",
"update_language": "Atualizar idioma",
"url": "URL",
"user_profile": "Perfil do usuário",
"user_profile_sub": "Convide usuários e gerencie sua conta."
},
"tools": {
"actions": "Ações de inventário",
"actions_set": {
"ensure_ids": "Garantir IDs de ativos",
"ensure_ids_button": "Garantir IDs de ativos",
"ensure_ids_sub": "Garante que todos os itens em seu inventário tenham um campo de asset_id válido. Isso é feito através da procura do mais alto asset_id no banco de dados e aplicando o próximo valor aos itens seguintes que não tenham valores de asset_id definidos. Isso é feito com base no campo created_at.",
"ensure_import_refs": "Garantir refs de importação",
"ensure_import_refs_button": "Garantir refs de importação",
"ensure_import_refs_sub": "Garante que todos os itens no seu inventário tenham um campo import_ref válido. Isso é feito gerando aleatoriamente uma sequência de 8 caracteres para cada item que não possuir um campo import_ref válido.",
"set_primary_photo": "Definir foto principal",
"set_primary_photo_button": "Definir foto principal",
"set_primary_photo_sub": "Na versão v0.10.0 do Homebox, o campo principal de foto foi adicionado aos anexos do tipo foto. Essa ação irá marcar o campo da imagem principal para a primeira imagem no array de anexos do banco de dados, se não estiver feito ainda.'<a class=\"link\" href=\"https://github.com/hay-kot/homebox/pull/576\">'Veja no GitHub PR #576'</a>'",
"zero_datetimes": "Zerar Data Hora do Item",
"zero_datetimes_button": "Zerar Data Hora do Item",
"zero_datetimes_sub": "Redefine o valor de hora de todos os itens do inventário para o início da data. Isso é para corrigir um bug que foi introduzido no início do desenvolvimento do site que causava o valor da hora ser armazenado errado, resultando em um erro na mostra da data/hora do item. '<a class=\"link\" href=\"https://github.com/hay-kot/homebox/issues/236\" target=\"_blank\">'Veja o item #236 do Github para mais detalhes.'</a>'"
},
"actions_sub": "Aplicar ações ao seu inventário em massa. Essas ações são irreversíveis. '<b>'Tenha cuidado'</b>'",
"import_export": "Importar/Exportar",
"import_export_set": {
"export": "Exportar inventário",
"export_button": "Exportar inventário",
"export_sub": "Exporta o formato CSV padrão para o Homebox. Isso irá exportar todos os itens do seu inventário.",
"import": "Importar inventário",
"import_button": "Importar inventário",
"import_sub": "Importa o formato CSV padrão para o Homebox. Sem uma coluna '<code>'HB.import_ref'</code>' , isso '<b>não</b>' vai sobrescrever nenhum item de seu inventário, apenas adicionar novos items. Linhas que tenham '<code>'HB.import_ref'</code>' colunas são mescladas em itens existentes com o mesmo import_ref, se existir."
},
"import_export_sub": "Importar e exportar seu inventário de ou para um arquivo CSV. Isso é útil para migrar o seu inventário para uma nova instância do Homebox.",
"reports": "Relatórios",
"reports_set": {
"asset_labels": "Etiquetas de ID de ativo",
"asset_labels_button": "Gerador de etiquetas",
"asset_labels_sub": "Gera um PDF imprimível das etiquetas no intervalo de ID de Ativo. Essas não são específicas do seu inventário, então você pode imprimir elas antes e aplicar no seu inventário depois quando receber os items.",
"bill_of_materials": "Lista de Materiais",
"bill_of_materials_button": "Gerar Lista de Materiais",
"bill_of_materials_sub": "Gera um arquivo CSV (Valores Separados por Vírgula) que pode ser importado para um programa de planilha. Esse é um sumário do seu inventário com informações básicas dos itens e dos preços."
},
"reports_sub": "Gere relatórios diferentes para o seu inventário."
}
}

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