mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 21:33:02 +01:00
Compare commits
163 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f386d1213f | ||
|
|
9b7b00e8f2 | ||
|
|
055f0219a8 | ||
|
|
9d3f3cf1da | ||
|
|
da8cc19838 | ||
|
|
a3d5485c1d | ||
|
|
865661097c | ||
|
|
ab48f55335 | ||
|
|
49f52cada4 | ||
|
|
059bc5f16c | ||
|
|
a8eab724f9 | ||
|
|
eed967e9b4 | ||
|
|
2eb421455b | ||
|
|
e0d86ce745 | ||
|
|
ba8f32cec8 | ||
|
|
4af6bf210e | ||
|
|
1de3ecda19 | ||
|
|
4a0039c838 | ||
|
|
02a34ed1a0 | ||
|
|
dc23a1ae23 | ||
|
|
4fea927880 | ||
|
|
ff874ac472 | ||
|
|
ba48a615cd | ||
|
|
5aa389b13c | ||
|
|
b500f6b51f | ||
|
|
2ea9ed0476 | ||
|
|
68ee701e46 | ||
|
|
dc88406bcc | ||
|
|
ca85d4b483 | ||
|
|
17ecfa2c66 | ||
|
|
cabaa07384 | ||
|
|
d9b05e872c | ||
|
|
bd60c36240 | ||
|
|
1fc9843450 | ||
|
|
4e260c5a8b | ||
|
|
55a9355046 | ||
|
|
5bee3dd429 | ||
|
|
a85c42b539 | ||
|
|
897f3842e0 | ||
|
|
791d390187 | ||
|
|
e85b1a44b1 | ||
|
|
ffd59d535c | ||
|
|
e32a1041b1 | ||
|
|
c16269bcb4 | ||
|
|
81d57d4c47 | ||
|
|
bdb3b5dc24 | ||
|
|
666903e663 | ||
|
|
24deb462a8 | ||
|
|
85b54c4ccf | ||
|
|
ca33d499ab | ||
|
|
52ebff7ca4 | ||
|
|
4cf0b0a9df | ||
|
|
0d92d2718d | ||
|
|
22937dd314 | ||
|
|
26a99b6bad | ||
|
|
4bf7f3e00c | ||
|
|
269c5ea7e3 | ||
|
|
bac6212188 | ||
|
|
7a7d00220a | ||
|
|
a20f94b68a | ||
|
|
7c4c373851 | ||
|
|
8b0684a0ca | ||
|
|
c903a9df13 | ||
|
|
a5d4e4f491 | ||
|
|
f3116e4729 | ||
|
|
c0b88142d8 | ||
|
|
19adc6a441 | ||
|
|
d9fa7a846c | ||
|
|
aa30fba7af | ||
|
|
edbb4b9fa0 | ||
|
|
7d8534ffc1 | ||
|
|
28b79beb76 | ||
|
|
8a7af9a98d | ||
|
|
47600ade0e | ||
|
|
2af0cfeb49 | ||
|
|
f6690dc80f | ||
|
|
9de5cf8c58 | ||
|
|
9e66bf0bc1 | ||
|
|
5f27f5d9cc | ||
|
|
0a1667ed24 | ||
|
|
6348aeac6e | ||
|
|
7e9bd7f44b | ||
|
|
cf780393c2 | ||
|
|
e94f436ba6 | ||
|
|
0c6be05b05 | ||
|
|
9297ac59ae | ||
|
|
983dedfdad | ||
|
|
d159908b91 | ||
|
|
1b53a5235c | ||
|
|
f3f709748e | ||
|
|
64ceffefe9 | ||
|
|
4b24653b86 | ||
|
|
6a0ebb76ea | ||
|
|
8e273730be | ||
|
|
5c09953e4c | ||
|
|
6866dc76c0 | ||
|
|
de16f09108 | ||
|
|
3f3ca345fd | ||
|
|
9275d2db9c | ||
|
|
d88b04b66f | ||
|
|
62e6b08baf | ||
|
|
6fa37cb474 | ||
|
|
d63d6e94dd | ||
|
|
0c8ce366eb | ||
|
|
3da3025935 | ||
|
|
d1a57e3ec5 | ||
|
|
969ef1941b | ||
|
|
214b16a26e | ||
|
|
405d0c7487 | ||
|
|
6800c2112e | ||
|
|
5fc7b3e25b | ||
|
|
88dc943b6b | ||
|
|
f8482b1c64 | ||
|
|
a6e49295e0 | ||
|
|
8ef1b8b6ce | ||
|
|
404791a344 | ||
|
|
073aade67f | ||
|
|
784cc409d4 | ||
|
|
9e3f82fbac | ||
|
|
19c6d4dec5 | ||
|
|
b18f0c790b | ||
|
|
fb62f51958 | ||
|
|
dafc6aa13f | ||
|
|
93f13b1e80 | ||
|
|
5de649d85f | ||
|
|
b37cf24f09 | ||
|
|
cf2edc8d34 | ||
|
|
f113de180b | ||
|
|
adb4b52752 | ||
|
|
baf8912dda | ||
|
|
489deda6a8 | ||
|
|
2fee607327 | ||
|
|
c428a22b5b | ||
|
|
42c01adb98 | ||
|
|
209bb2932c | ||
|
|
ec9cdb391a | ||
|
|
a6aafeb374 | ||
|
|
ffb538ef21 | ||
|
|
15925de2f0 | ||
|
|
25d72044e9 | ||
|
|
6b598383d3 | ||
|
|
25c76522d6 | ||
|
|
c0e2aa5c62 | ||
|
|
d0b9f742ae | ||
|
|
80d56829c5 | ||
|
|
0946310f60 | ||
|
|
7c855cf55d | ||
|
|
0ab95fb670 | ||
|
|
1e81b4bab4 | ||
|
|
67c50068d9 | ||
|
|
c3628e36f7 | ||
|
|
526799c6da | ||
|
|
4ef7529533 | ||
|
|
b06d670dff | ||
|
|
229d4db996 | ||
|
|
184be32f3a | ||
|
|
8058743c2f | ||
|
|
a6bdadedb1 | ||
|
|
d08dafd965 | ||
|
|
fef026ed47 | ||
|
|
6001bf90c5 | ||
|
|
f51de34355 | ||
|
|
4e9647651c |
16
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
16
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -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:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -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
|
||||
|
||||
@@ -2,7 +2,7 @@ name: Docker publish rootless
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '00 6 * * *'
|
||||
- cron: '00 0 * * *'
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
|
||||
2
.github/workflows/docker-publish.yaml
vendored
2
.github/workflows/docker-publish.yaml
vendored
@@ -2,7 +2,7 @@ name: Docker publish
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '00 6 * * *'
|
||||
- cron: '00 0 * * *'
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
65
backend/app/api/handlers/v1/v1_ctrl_maintenance.go
Normal file
65
backend/app/api/handlers/v1/v1_ctrl_maintenance.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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...))
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2995,4 +3083,4 @@
|
||||
"in": "header"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
111
backend/go.sum
111
backend/go.sum
@@ -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=
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ type (
|
||||
LocationOut struct {
|
||||
Parent *LocationSummary `json:"parent,omitempty"`
|
||||
LocationSummary
|
||||
Children []LocationSummary `json:"children"`
|
||||
TotalPrice float64 `json:"totalPrice"`
|
||||
Children []LocationSummary `json:"children"`
|
||||
TotalPrice float64 `json:"totalPrice"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
72
backend/internal/data/repo/repo_maintenance.go
Normal file
72
backend/internal/data/repo/repo_maintenance.go
Normal 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
|
||||
}
|
||||
@@ -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{}),
|
||||
if filters.Status == MaintenanceFilterStatusScheduled {
|
||||
query = query.Where(maintenanceentry.Or(
|
||||
maintenanceentry.DateIsNil(),
|
||||
maintenanceentry.DateEQ(time.Time{}),
|
||||
))
|
||||
} else if query.Scheduled {
|
||||
q = q.Where(maintenanceentry.And(
|
||||
maintenanceentry.Or(
|
||||
} else if filters.Status == MaintenanceFilterStatusCompleted {
|
||||
query = query.Where(
|
||||
maintenanceentry.Not(maintenanceentry.Or(
|
||||
maintenanceentry.DateIsNil(),
|
||||
maintenanceentry.DateEQ(time.Time{}),
|
||||
),
|
||||
maintenanceentry.ScheduledDateNotNil(),
|
||||
maintenanceentry.ScheduledDateNEQ(time.Time{}),
|
||||
))
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
]
|
||||
}
|
||||
|
||||
25
docs/.vitepress/menus/en.mts
Normal file
25
docs/.vitepress/menus/en.mts
Normal 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'}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2995,4 +3083,4 @@
|
||||
"in": "header"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
docs/en/configure-homebox.md
Normal file
59
docs/en/configure-homebox.md
Normal 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
|
||||
```
|
||||
:::
|
||||
@@ -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.
|
||||
|
||||
[](http://translate.sysadminsmedia.com/engage/homebox/)
|
||||
|
||||
|
||||
BIN
docs/en/images/home-screen.png
Normal file
BIN
docs/en/images/home-screen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
@@ -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 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
99
docs/en/installation.md
Normal 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)
|
||||
80
docs/en/organizing-items.md
Normal file
80
docs/en/organizing-items.md
Normal 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!
|
||||
@@ -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**.
|
||||
@@ -11,6 +11,7 @@ module.exports = {
|
||||
"@nuxtjs/eslint-config-typescript",
|
||||
"plugin:vue/vue3-recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:tailwindcss/recommended",
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
|
||||
@@ -27,4 +27,4 @@
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #9B9B9B;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<li
|
||||
v-for="(obj, idx) in items"
|
||||
:key="idx"
|
||||
:class="{
|
||||
bordered: selected[idx],
|
||||
}"
|
||||
>
|
||||
<button type="button" @click="toggle(idx)">
|
||||
{{ name != "" ? obj[name] : obj }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="m-2">
|
||||
<input v-model="search" placeholder="Search…" class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
<ul class="max-h-60 overflow-y-scroll">
|
||||
<li
|
||||
v-for="(obj, idx) in filteredItems"
|
||||
:key="idx"
|
||||
:class="{
|
||||
bordered: selected.includes(obj[props.uniqueField]),
|
||||
}"
|
||||
>
|
||||
<button type="button" @click="toggle(obj[props.uniqueField])">
|
||||
{{ name != "" ? obj[name] : obj }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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];
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
<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">
|
||||
</div>
|
||||
<div class="modal-action mb-6">
|
||||
<div>
|
||||
<label for="photo" class="btn">{{ $t("components.item.create_modal.photo_button") }}</label>
|
||||
<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" />
|
||||
</template>
|
||||
<template v-if="form.preview">
|
||||
<p class="mb-0">File name: {{ form.photo?.name }}</p>
|
||||
<img
|
||||
:src="form.preview"
|
||||
class="h-[100px] w-full rounded-t border-gray-300 object-cover shadow-sm"
|
||||
alt="Uploaded Photo"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
<p class="text-sm text-center mt-4">
|
||||
<p class="mt-4 text-center text-sm">
|
||||
use <kbd class="kbd kbd-xs">Shift</kbd> + <kbd class="kbd kbd-xs"> Enter </kbd> to create and add another
|
||||
</p>
|
||||
</BaseModal>
|
||||
@@ -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 = "";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
164
frontend/components/Maintenance/EditModal.vue
Normal file
164
frontend/components/Maintenance/EditModal.vue
Normal 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>
|
||||
201
frontend/components/Maintenance/ListView.vue
Normal file
201
frontend/components/Maintenance/ListView.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()" />
|
||||
|
||||
@@ -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="{
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<template>
|
||||
<div class="grow-1 max-w-full"></div>
|
||||
<div class="max-w-full"></div>
|
||||
</template>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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) {
|
||||
|
||||
30
frontend/lib/api/classes/maintenance.ts
Normal file
30
frontend/lib/api/classes/maintenance.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
182
frontend/locales/ca.json
Normal 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' 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,128 +1,242 @@
|
||||
{
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"change_warning": "Behavior for imports with existing import_refs has changed. If an import_ref is present in the CSV file, the \nitem will be updated with the values in the CSV file.",
|
||||
"description": "Import a CSV file containing your items, labels, and locations. See documentation for more information on the \nrequired format.",
|
||||
"title": "Import CSV File",
|
||||
"upload": "Upload"
|
||||
}
|
||||
"components": {
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"change_warning": "Behavior for imports with existing import_refs has changed. If an import_ref is present in the CSV file, the \nitem will be updated with the values in the CSV file.",
|
||||
"description": "Import a CSV file containing your items, labels, and locations. See documentation for more information on the \nrequired format.",
|
||||
"title": "Import CSV File",
|
||||
"upload": "Upload"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"page_qr_code": {
|
||||
"page_url": "Page URL"
|
||||
},
|
||||
"password_score": {
|
||||
"password_strength": "Password Strength"
|
||||
}
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"photo_button": "Photo 📷",
|
||||
"title": "Create Item"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"card": "Card",
|
||||
"items": "Items",
|
||||
"no_items": "No Items to Display",
|
||||
"table": "Table"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Create Label"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Create Location"
|
||||
},
|
||||
"tree": {
|
||||
"no_locations": "No locations available. Add new locations through the\n `<`span class=\"link-primary\"`>`Create`<`/span`>` button on the navigation bar."
|
||||
}
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"page_qr_code": {
|
||||
"page_url": "Page URL"
|
||||
},
|
||||
"password_score": {
|
||||
"password_strength": "Password Strength"
|
||||
}
|
||||
"build": "Build: { build }",
|
||||
"confirm": "Confirm",
|
||||
"create": "Create",
|
||||
"create_and_add": "Create and Add Another",
|
||||
"created": "Created",
|
||||
"email": "Email",
|
||||
"follow_dev": "Follow the Developer",
|
||||
"github": "GitHub Project",
|
||||
"items": "Items",
|
||||
"join_discord": "Join the Discord",
|
||||
"labels": "Labels",
|
||||
"locations": "Locations",
|
||||
"name": "Name",
|
||||
"password": "Password",
|
||||
"read_docs": "Read the Docs",
|
||||
"search": "Search",
|
||||
"sign_out": "Sign Out",
|
||||
"submit": "Submit",
|
||||
"version": "Version: { version }",
|
||||
"welcome": "Welcome, { username }"
|
||||
},
|
||||
"item": {
|
||||
"create_modal": {
|
||||
"title": "Create Item",
|
||||
"photo_button": "Photo \uD83D\uDCF7"
|
||||
},
|
||||
"view": {
|
||||
"selectable": {
|
||||
"card": "Card",
|
||||
"items": "Items",
|
||||
"no_items": "No Items to Display",
|
||||
"table": "Table"
|
||||
}
|
||||
}
|
||||
"index": {
|
||||
"disabled_registration": "Registration Disabled",
|
||||
"dont_join_group": "Don't want to join a group?",
|
||||
"joining_group": "You're Joining an Existing Group!",
|
||||
"login": "Login",
|
||||
"register": "Register",
|
||||
"remember_me": "Remember Me",
|
||||
"set_email": "What's your email?",
|
||||
"set_name": "What's your name?",
|
||||
"set_password": "Set your password",
|
||||
"tagline": "Track, Organize, and Manage your Things."
|
||||
},
|
||||
"label": {
|
||||
"create_modal": {
|
||||
"title": "Create Label"
|
||||
}
|
||||
"items": {
|
||||
"add": "Add",
|
||||
"created_at": "Created At",
|
||||
"custom_fields": "Custom Fields",
|
||||
"field_selector": "Field Selector",
|
||||
"field_value": "Field Value",
|
||||
"first": "First",
|
||||
"include_archive": "Include Archived Items",
|
||||
"last": "Last",
|
||||
"negate_labels": "Negate Selected Labels",
|
||||
"next_page": "Next Page",
|
||||
"no_results": "No Items Found",
|
||||
"options": "Options",
|
||||
"order_by": "Order By",
|
||||
"pages": "Page { page } of { totalPages }",
|
||||
"prev_page": "Previous Page",
|
||||
"query_id": "Querying Asset ID Number: { id }",
|
||||
"reset_search": "Reset Search",
|
||||
"results": "{ total } Results",
|
||||
"tip_1": "Location and label filters use the 'OR' operation. If more than one is selected only one will be\n required for a match.",
|
||||
"tip_2": "Searches prefixed with '#'' will query for a asset ID (example '#000-001')",
|
||||
"tip_3": "Field filters use the 'OR' operation. If more than one is selected only one will be required for a\n match.",
|
||||
"tips": "Tips",
|
||||
"tips_sub": "Search Tips",
|
||||
"updated_at": "Updated At"
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Create Location"
|
||||
}
|
||||
"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",
|
||||
"currency_format": "Currency Format",
|
||||
"current_password": "Current Password",
|
||||
"delete_account": "Delete Account",
|
||||
"delete_account_sub": "Delete your account and all its associated data. This can not be undone.",
|
||||
"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",
|
||||
"test": "Test",
|
||||
"theme_settings": "Theme Settings",
|
||||
"theme_settings_sub": "Theme settings are stored in your browser's local storage. You can change the theme at any time. If you're\n having trouble setting your theme try refreshing your browser.",
|
||||
"update_group": "Update Group",
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"build": "Build: { build }",
|
||||
"confirm": "Confirm",
|
||||
"create": "Create",
|
||||
"create_and_add": "Create and Add Another",
|
||||
"created": "Created",
|
||||
"email": "Email",
|
||||
"follow_dev": "Follow the Developer",
|
||||
"github": "GitHub Project",
|
||||
"items": "Items",
|
||||
"join_discord": "Join the Discord",
|
||||
"labels": "Labels",
|
||||
"locations": "Locations",
|
||||
"name": "Name",
|
||||
"password": "Password",
|
||||
"read_docs": "Read the Docs",
|
||||
"search": "Search",
|
||||
"sign_out": "Sign Out",
|
||||
"submit": "Submit",
|
||||
"version": "Version: { version }",
|
||||
"welcome": "Welcome, { username }"
|
||||
},
|
||||
"index": {
|
||||
"disabled_registration": "Registration Disabled",
|
||||
"dont_join_group": "Don't want to join a group?",
|
||||
"joining_group": "You're Joining an Existing Group!",
|
||||
"login": "Login",
|
||||
"register": "Register",
|
||||
"remember_me": "Remember Me",
|
||||
"set_email": "What's your email?",
|
||||
"set_name": "What's your name?",
|
||||
"set_password": "Set your password",
|
||||
"tagline": "Track, Organize, and Manage your Things."
|
||||
},
|
||||
"items": {
|
||||
"add": "Add",
|
||||
"created_at": "Created At",
|
||||
"custom_fields": "Custom Fields",
|
||||
"field_selector": "Field Selector",
|
||||
"field_value": "Field Value",
|
||||
"first": "First",
|
||||
"include_archive": "Include Archived Items",
|
||||
"last": "Last",
|
||||
"negate_labels": "Negate Selected Labels",
|
||||
"next_page": "Next Page",
|
||||
"no_results": "No Items Found",
|
||||
"options": "Options",
|
||||
"order_by": "Order By",
|
||||
"pages": "Page { page } of { totalPages }",
|
||||
"prev_page": "Previous Page",
|
||||
"query_id": "Querying Asset ID Number: { id }",
|
||||
"reset_search": "Reset Search",
|
||||
"results": "{ total } Results",
|
||||
"tip_1": "Location and label filters use the 'OR' operation. If more than one is selected only one will be\n required for a match.",
|
||||
"tip_2": "Searches prefixed with '#'' will query for a asset ID (example '#000-001')",
|
||||
"tip_3": "Field filters use the 'OR' operation. If more than one is selected only one will be required for a\n match.",
|
||||
"tips": "Tips",
|
||||
"tips_sub": "Search Tips",
|
||||
"updated_at": "Updated At"
|
||||
},
|
||||
"profile": {
|
||||
"active": "Active",
|
||||
"change_password": "Change Password",
|
||||
"currency_format": "Currency Format",
|
||||
"current_password": "Current Password",
|
||||
"delete_account": "Delete Account",
|
||||
"delete_account_sub": "Delete your account and all its associated data. This can not be undone.",
|
||||
"enabled": "Enabled",
|
||||
"gen_invite": "Generate Invite Link",
|
||||
"group_settings": "Group Settings",
|
||||
"group_settings_sub": "Shared Group Settings. You may need to refresh your browser for some settings to apply.",
|
||||
"inactive": "Inactive",
|
||||
"new_password": "New Password",
|
||||
"notifier_modal": "{ type, select, true {Edit} false {Create} other {Other}} Notifier",
|
||||
"notifiers": "Notifiers",
|
||||
"notifiers_sub": "Get notifications for upcoming maintenance reminders",
|
||||
"test": "Test",
|
||||
"theme_settings": "Theme Settings",
|
||||
"theme_settings_sub": "Theme settings are stored in your browser's local storage. You can change the theme at any time. If you're\n having trouble setting your theme try refreshing your browser.",
|
||||
"update_group": "Update Group",
|
||||
"url": "URL",
|
||||
"user_profile": "User Profile",
|
||||
"user_profile_sub": "Invite users, and manage your account."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 d’importations 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 d’informations 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. d’articles à afficher",
|
||||
"table": "Tableau",
|
||||
"card": "Carte"
|
||||
}
|
||||
},
|
||||
"create_modal": {
|
||||
"title": "Créer un article",
|
||||
"photo_button": "Photo 📷"
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"create_modal": {
|
||||
"title": "Créer un emplacement"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"password_score": {
|
||||
"password_strength": "Solidité du mot de passe"
|
||||
},
|
||||
"page_qr_code": {
|
||||
"page_url": "URL de la page"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"import_dialog": {
|
||||
"title": "Importer un fichier CSV",
|
||||
"upload": "Téléverser",
|
||||
"description": "Importer un fichier CSV contenant vos articles, étiquettes, et emplacements. Voir la documentation pour plus d’informations sur le format requis.",
|
||||
"change_warning": "Le comportement lors d’importations 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."
|
||||
"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."
|
||||
}
|
||||
}
|
||||
},
|
||||
"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 d’invitation",
|
||||
"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 qu’ils 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": ""
|
||||
"global": {
|
||||
"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 }"
|
||||
},
|
||||
"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 d’emplacement et d’étiquette utilisent l’opérateur « OU ».\nUn seul des filtres n’a 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 l’opérateur « OU ».\nUn seul des filtres n’a 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 l’en-tête} false {Afficher l’en-tête} other {Not Hit}}",
|
||||
"enabled": "Activé",
|
||||
"gen_invite": "Générer un lien d’invitation",
|
||||
"group_settings": "Paramètres du groupe",
|
||||
"group_settings_sub": "Paramètres du groupe partagé. Il peut être nécessaire de recharger la page pour qu’ils 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 d’inventaire",
|
||||
"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 l’identifiant 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 d’identifiant d’actif",
|
||||
"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
242
frontend/locales/hu.json
Normal 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."
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
@@ -61,7 +62,7 @@
|
||||
"submit": "Invia",
|
||||
"version": "Versione: { version }",
|
||||
"welcome": "Benvenuto, { username }"
|
||||
},
|
||||
},
|
||||
"index": {
|
||||
"disabled_registration": "Registrazione Disabilitata",
|
||||
"dont_join_group": "Non vuoi unirti a un gruppo?",
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
128
frontend/locales/pl.json
Normal 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."
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user