mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 13:23:14 +01:00
Implement maintenance view (#235)
* implement backend to query maintenances * improve backend API for maintenances * add itemId and itemName (v1/maintenances) * fix remaining todo in backend (/v1/maintenances) * refactor: extract MaintenanceEditModal * first draft (to be cleaned) * revert dependency updates (not required) * translation + fix deletion * fix main menu css issues * fix main menu "create" button (css) * enhancement: make item name clickable * fix: add page title (+ translate existing ones) * fix: missing toast translation (when updating) * bug fix: missing group check in backend (for new endpoint) * backport from following PR (to avoid useless changes) * maintenances => maintenance
This commit is contained in:
@@ -13,7 +13,7 @@ import (
|
||||
// HandleMaintenanceLogGet godoc
|
||||
//
|
||||
// @Summary Get Maintenance Log
|
||||
// @Tags Maintenance
|
||||
// @Tags Item Maintenance
|
||||
// @Produce json
|
||||
// @Success 200 {object} repo.MaintenanceLog
|
||||
// @Router /v1/items/{id}/maintenance [GET]
|
||||
@@ -30,7 +30,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 +44,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)
|
||||
}
|
||||
@@ -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,7 +917,7 @@ const docTemplate = `{
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Maintenance"
|
||||
"Item Maintenance"
|
||||
],
|
||||
"summary": "Get Maintenance Log",
|
||||
"responses": {
|
||||
@@ -939,7 +939,7 @@ const docTemplate = `{
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Maintenance"
|
||||
"Item Maintenance"
|
||||
],
|
||||
"summary": "Create Maintenance Entry",
|
||||
"parameters": [
|
||||
@@ -963,60 +963,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 +1355,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 +2520,9 @@ const docTemplate = `{
|
||||
"parent": {
|
||||
"$ref": "#/definitions/repo.LocationSummary"
|
||||
},
|
||||
"totalPrice": {
|
||||
"type": "number"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -2611,6 +2658,49 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.MaintenanceEntryWithDetails": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completedDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"cost": {
|
||||
"type": "string",
|
||||
"example": "0"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"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.MaintenanceLog": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -910,7 +910,7 @@
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Maintenance"
|
||||
"Item Maintenance"
|
||||
],
|
||||
"summary": "Get Maintenance Log",
|
||||
"responses": {
|
||||
@@ -932,7 +932,7 @@
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Maintenance"
|
||||
"Item Maintenance"
|
||||
],
|
||||
"summary": "Create Maintenance Entry",
|
||||
"parameters": [
|
||||
@@ -956,60 +956,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 +1348,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,6 +2651,49 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.MaintenanceEntryWithDetails": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completedDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"cost": {
|
||||
"type": "string",
|
||||
"example": "0"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"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.MaintenanceLog": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -2710,9 +2797,6 @@
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
},
|
||||
"totalPrice": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2995,4 +3079,4 @@
|
||||
"in": "header"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,6 +390,8 @@ definitions:
|
||||
type: string
|
||||
parent:
|
||||
$ref: '#/definitions/repo.LocationSummary'
|
||||
totalPrice:
|
||||
type: number
|
||||
updatedAt:
|
||||
type: string
|
||||
type: object
|
||||
@@ -479,6 +481,36 @@ definitions:
|
||||
scheduledDate:
|
||||
type: string
|
||||
type: object
|
||||
repo.MaintenanceEntryWithDetails:
|
||||
properties:
|
||||
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.MaintenanceLog:
|
||||
properties:
|
||||
costAverage:
|
||||
@@ -1228,7 +1260,7 @@ paths:
|
||||
- Bearer: []
|
||||
summary: Get Maintenance Log
|
||||
tags:
|
||||
- Maintenance
|
||||
- Item Maintenance
|
||||
post:
|
||||
parameters:
|
||||
- description: Entry Data
|
||||
@@ -1248,39 +1280,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 +1581,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:
|
||||
|
||||
@@ -110,6 +110,8 @@ 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-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/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
@@ -119,6 +121,8 @@ github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJm
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
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=
|
||||
@@ -136,6 +140,10 @@ 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=
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
@@ -910,7 +910,7 @@
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Maintenance"
|
||||
"Item Maintenance"
|
||||
],
|
||||
"summary": "Get Maintenance Log",
|
||||
"responses": {
|
||||
@@ -932,7 +932,7 @@
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Maintenance"
|
||||
"Item Maintenance"
|
||||
],
|
||||
"summary": "Create Maintenance Entry",
|
||||
"parameters": [
|
||||
@@ -956,60 +956,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 +1348,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,6 +2651,49 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repo.MaintenanceEntryWithDetails": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"completedDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"cost": {
|
||||
"type": "string",
|
||||
"example": "0"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"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.MaintenanceLog": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -2710,9 +2797,6 @@
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
},
|
||||
"totalPrice": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2995,4 +3079,4 @@
|
||||
"in": "header"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
139
frontend/components/Maintenance/EditModal.vue
Normal file
139
frontend/components/Maintenance/EditModal.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<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");
|
||||
}
|
||||
|
||||
defineExpose({ openCreateModal, openUpdateModal, deleteEntry });
|
||||
</script>
|
||||
@@ -12,15 +12,15 @@
|
||||
<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">
|
||||
<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 drawer-button fixed top-0 z-[99] bg-primary shadow-md lg:hidden">
|
||||
<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="flex text-3xl font-bold tracking-tight text-base-100">
|
||||
<h2 class="text-base-100 flex text-3xl font-bold tracking-tight">
|
||||
HomeB
|
||||
<AppLogo class="-mb-3 w-8" />
|
||||
x
|
||||
@@ -29,7 +29,7 @@
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
<footer v-if="status" class="bottom-0 w-full bg-base-300 pb-4 text-center 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="flex w-60 flex-col bg-base-200 py-5 md:py-10">
|
||||
<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="w-24 rounded-full bg-base-300 p-4 text-neutral-content">
|
||||
<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 mb-6 w-40">
|
||||
<div class="dropdown visible w-40">
|
||||
<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="-ml-1 mr-1" />
|
||||
</span>
|
||||
{{ $t("global.create") }}
|
||||
</label>
|
||||
<ul tabindex="0" class="dropdown-content menu rounded-box w-40 bg-base-100 p-2 shadow">
|
||||
<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="menu mx-auto flex w-40 flex-col gap-2">
|
||||
<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"
|
||||
@@ -89,7 +89,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Bottom -->
|
||||
<button class="rounded-btn mx-2 mt-auto p-3 hover:bg-base-300" @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,6 +110,9 @@
|
||||
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();
|
||||
@@ -164,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,7 +10,6 @@ import type {
|
||||
ItemUpdate,
|
||||
MaintenanceEntry,
|
||||
MaintenanceEntryCreate,
|
||||
MaintenanceEntryUpdate,
|
||||
MaintenanceLog,
|
||||
} from "../types/data-contracts";
|
||||
import type { AttachmentTypes, PaginationResult } from "../types/non-generated";
|
||||
@@ -71,7 +70,7 @@ type MaintenanceEntryQuery = {
|
||||
completed?: boolean;
|
||||
};
|
||||
|
||||
export class MaintenanceAPI extends BaseAPI {
|
||||
export class ItemMaintenanceAPI extends BaseAPI {
|
||||
getLog(itemId: string, q: MaintenanceEntryQuery = {}) {
|
||||
return this.http.get<MaintenanceLog>({ url: route(`/items/${itemId}/maintenance`, q) });
|
||||
}
|
||||
@@ -82,29 +81,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,6 +288,24 @@ export interface MaintenanceEntryUpdate {
|
||||
scheduledDate: Date | 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 MaintenanceLog {
|
||||
costAverage: number;
|
||||
costTotal: number;
|
||||
@@ -330,7 +348,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);
|
||||
|
||||
@@ -101,6 +101,51 @@
|
||||
"tips_sub": "Search Tips",
|
||||
"updated_at": "Updated At"
|
||||
},
|
||||
"menu":{
|
||||
"home": "Home",
|
||||
"locations": "Locations",
|
||||
"search": "Search",
|
||||
"maintenance": "Maintenance",
|
||||
"profile": "Profile",
|
||||
"tools": "Tools"
|
||||
},
|
||||
"maintenance": {
|
||||
"total_entries": "Total Entries",
|
||||
"total_cost": "Total Cost",
|
||||
"monthly_average": "Monthly Average",
|
||||
"list":
|
||||
{
|
||||
"new": "New",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"create_first": "Create Your First Entry"
|
||||
},
|
||||
"modal":
|
||||
{
|
||||
"new_title": "New Entry",
|
||||
"edit_title": "Edit Entry",
|
||||
"new_action": "Create",
|
||||
"edit_action": "Update",
|
||||
"delete_confirmation": "Are you sure you want to delete this entry?",
|
||||
"entry_name": "Entry Name",
|
||||
"completed_date": "Completed Date",
|
||||
"scheduled_date": "Scheduled Date",
|
||||
"notes": "Notes",
|
||||
"cost": "Cost"
|
||||
},
|
||||
"filter":
|
||||
{
|
||||
"scheduled": "Scheduled",
|
||||
"completed": "Completed",
|
||||
"both": "Both"
|
||||
},
|
||||
"toast":
|
||||
{
|
||||
"failed_to_delete": "Failed to delete entry",
|
||||
"failed_to_create": "Failed to create entry",
|
||||
"failed_to_update" : "Failed to update entry"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"active": "Active",
|
||||
"change_password": "Change Password",
|
||||
|
||||
@@ -121,6 +121,51 @@
|
||||
"zh-MO": "Chinois (Macao)",
|
||||
"zh-TW": "Chinois (traditionnel)"
|
||||
},
|
||||
"menu":{
|
||||
"home": "Accueil",
|
||||
"locations": "Emplacements",
|
||||
"search": "Recherche",
|
||||
"maintenance": "Maintenance",
|
||||
"profile": "Profil",
|
||||
"tools": "Outils"
|
||||
},
|
||||
"maintenance": {
|
||||
"total_entries": "Nombre d'entrées",
|
||||
"total_cost": "Coût total",
|
||||
"monthly_average": "Moyenne mensuelle",
|
||||
"list":
|
||||
{
|
||||
"new": "Ajouter",
|
||||
"edit": "Modifier",
|
||||
"delete": "Supprimer",
|
||||
"create_first": "Créer votre première entrée"
|
||||
},
|
||||
"modal":
|
||||
{
|
||||
"new_title": "Nouvelle entrée",
|
||||
"edit_title": "Modifier l'entrée",
|
||||
"new_action": "Créer",
|
||||
"edit_action": "Modifier",
|
||||
"delete_confirmation": "Êtes-vous certain de vouloir supprimer cette entrée ?",
|
||||
"entry_name": "Nom",
|
||||
"completed_date": "Date d'achèvement",
|
||||
"scheduled_date": "Date prévue",
|
||||
"notes": "Notes",
|
||||
"cost": "Coût"
|
||||
},
|
||||
"filter":
|
||||
{
|
||||
"scheduled": "Prévues",
|
||||
"completed": "Terminées",
|
||||
"both": "Toutes"
|
||||
},
|
||||
"toast":
|
||||
{
|
||||
"failed_to_delete": "Échec de création de l'entrée",
|
||||
"failed_to_create": "Échec de suppression de l'entrée",
|
||||
"failed_to_update" : "Échec de mise à jour de l'entrée"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"active": "Actif",
|
||||
"change_password": "Changer de mot de passe",
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import DatePicker from "~~/components/Form/DatePicker.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { StatsFormat } from "~~/components/global/StatCard/types";
|
||||
import type { ItemOut, MaintenanceEntry } from "~~/lib/api/types/data-contracts";
|
||||
import MdiPost from "~icons/mdi/post";
|
||||
import type { ItemOut } from "~~/lib/api/types/data-contracts";
|
||||
import MdiPlus from "~icons/mdi/plus";
|
||||
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 MdiWrenchClock from "~icons/mdi/wrench-clock";
|
||||
import MaintenanceEditModal from "~~/components/Maintenance/EditModal.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
item: ItemOut;
|
||||
}>();
|
||||
@@ -19,6 +20,8 @@
|
||||
|
||||
const scheduled = ref(true);
|
||||
|
||||
const maintenanceEditModal = ref<InstanceType<typeof MaintenanceEditModal>>();
|
||||
|
||||
watch(
|
||||
() => scheduled.value,
|
||||
() => {
|
||||
@@ -44,167 +47,36 @@
|
||||
return [
|
||||
{
|
||||
id: "count",
|
||||
title: "Total Entries",
|
||||
title: t("maintenance.total_entries"),
|
||||
value: count.value || 0,
|
||||
type: "number" as StatsFormat,
|
||||
},
|
||||
{
|
||||
id: "total",
|
||||
title: "Total Cost",
|
||||
title: t("maintenance.total_cost"),
|
||||
value: log.value.costTotal || 0,
|
||||
type: "currency" as StatsFormat,
|
||||
},
|
||||
{
|
||||
id: "average",
|
||||
title: "Monthly Average",
|
||||
title: t("maintenance.monthly_average"),
|
||||
value: log.value.costAverage || 0,
|
||||
type: "currency" as StatsFormat,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const entry = reactive({
|
||||
id: null as string | null,
|
||||
modal: false,
|
||||
name: "",
|
||||
completedDate: null as Date | null,
|
||||
scheduledDate: null as Date | null,
|
||||
description: "",
|
||||
cost: "",
|
||||
});
|
||||
|
||||
function newEntry() {
|
||||
entry.modal = true;
|
||||
}
|
||||
|
||||
function resetEntry() {
|
||||
console.log("Resetting entry");
|
||||
entry.id = null;
|
||||
entry.name = "";
|
||||
entry.completedDate = null;
|
||||
entry.scheduledDate = null;
|
||||
entry.description = "";
|
||||
entry.cost = "";
|
||||
}
|
||||
|
||||
watch(
|
||||
() => entry.modal,
|
||||
(v, pv) => {
|
||||
if (pv === true && v === false) {
|
||||
resetEntry();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Calls either edit or create depending on entry.id being set
|
||||
async function dispatchFormSubmit() {
|
||||
if (entry.id) {
|
||||
await editEntry();
|
||||
return;
|
||||
}
|
||||
|
||||
await createEntry();
|
||||
}
|
||||
|
||||
async function createEntry() {
|
||||
const { error } = await api.items.maintenance.create(props.item.id, {
|
||||
name: entry.name,
|
||||
completedDate: entry.completedDate ?? "",
|
||||
scheduledDate: entry.scheduledDate ?? "",
|
||||
description: entry.description,
|
||||
cost: parseFloat(entry.cost) ? entry.cost : "0",
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast.error("Failed to create entry");
|
||||
return;
|
||||
}
|
||||
|
||||
entry.modal = false;
|
||||
|
||||
refreshLog();
|
||||
resetEntry();
|
||||
}
|
||||
|
||||
const confirm = useConfirm();
|
||||
|
||||
async function deleteEntry(id: string) {
|
||||
const result = await confirm.open("Are you sure you want to delete this entry?");
|
||||
if (result.isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { error } = await api.items.maintenance.delete(props.item.id, id);
|
||||
|
||||
if (error) {
|
||||
toast.error("Failed to delete entry");
|
||||
return;
|
||||
}
|
||||
refreshLog();
|
||||
}
|
||||
|
||||
function openEditDialog(e: MaintenanceEntry) {
|
||||
entry.id = e.id;
|
||||
entry.name = e.name;
|
||||
entry.completedDate = new Date(e.completedDate);
|
||||
entry.scheduledDate = new Date(e.scheduledDate);
|
||||
entry.description = e.description;
|
||||
entry.cost = e.cost;
|
||||
entry.modal = true;
|
||||
}
|
||||
|
||||
async function editEntry() {
|
||||
if (!entry.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { error } = await api.items.maintenance.update(props.item.id, entry.id, {
|
||||
name: entry.name,
|
||||
completedDate: entry.completedDate ?? "null",
|
||||
scheduledDate: entry.scheduledDate ?? "null",
|
||||
description: entry.description,
|
||||
cost: entry.cost,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
toast.error("Failed to update entry");
|
||||
return;
|
||||
}
|
||||
|
||||
entry.modal = false;
|
||||
refreshLog();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="log">
|
||||
<BaseModal v-model="entry.modal">
|
||||
<template #title>
|
||||
{{ entry.id ? "Edit Entry" : "New Entry" }}
|
||||
</template>
|
||||
<form @submit.prevent="dispatchFormSubmit">
|
||||
<FormTextField v-model="entry.name" autofocus label="Entry Name" />
|
||||
<DatePicker v-model="entry.completedDate" label="Completed Date" />
|
||||
<DatePicker v-model="entry.scheduledDate" label="Scheduled Date" />
|
||||
<FormTextArea v-model="entry.description" label="Notes" />
|
||||
<FormTextField v-model="entry.cost" autofocus label="Cost" />
|
||||
<div class="flex justify-end py-2">
|
||||
<BaseButton type="submit" class="ml-2 mt-2">
|
||||
<template #icon>
|
||||
<MdiPost />
|
||||
</template>
|
||||
{{ entry.id ? "Update" : "Create" }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</form>
|
||||
</BaseModal>
|
||||
<MaintenanceEditModal ref="maintenanceEditModal" @changed="refreshLog"></MaintenanceEditModal>
|
||||
|
||||
<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 block border-l-primary shadow-xl"
|
||||
class="stats border-l-primary block shadow-xl"
|
||||
:title="stat.title"
|
||||
:value="stat.value"
|
||||
:type="stat.type"
|
||||
@@ -213,17 +85,17 @@
|
||||
<div class="flex">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm" :class="`${scheduled ? 'btn-active' : ''}`" @click="scheduled = true">
|
||||
Scheduled
|
||||
{{ $t("maintenance.filter.scheduled") }}
|
||||
</button>
|
||||
<button class="btn btn-sm" :class="`${scheduled ? '' : 'btn-active'}`" @click="scheduled = false">
|
||||
Completed
|
||||
{{ $t("maintenance.filter.completed") }}
|
||||
</button>
|
||||
</div>
|
||||
<BaseButton class="ml-auto" size="sm" @click="newEntry()">
|
||||
<BaseButton class="ml-auto" size="sm" @click="maintenanceEditModal?.openCreateModal(props.item.id)">
|
||||
<template #icon>
|
||||
<MdiPlus />
|
||||
</template>
|
||||
New
|
||||
{{ $t("maintenance.list.new") }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div class="container space-y-6">
|
||||
@@ -254,28 +126,30 @@
|
||||
<Markdown :source="e.description" />
|
||||
</div>
|
||||
<div class="flex justify-end gap-1 p-4">
|
||||
<BaseButton size="sm" @click="openEditDialog(e)">
|
||||
<BaseButton size="sm" @click="maintenanceEditModal?.openUpdateModal(e)">
|
||||
<template #icon>
|
||||
<MdiEdit />
|
||||
</template>
|
||||
Edit
|
||||
{{ $t("maintenance.list.edit") }}
|
||||
</BaseButton>
|
||||
<BaseButton size="sm" @click="deleteEntry(e.id)">
|
||||
<BaseButton size="sm" @click="maintenanceEditModal?.deleteEntry(e.id)">
|
||||
<template #icon>
|
||||
<MdiDelete />
|
||||
</template>
|
||||
Delete
|
||||
{{ $t("maintenance.list.delete") }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</BaseCard>
|
||||
<div class="hidden first:block">
|
||||
<button
|
||||
type="button"
|
||||
class="relative block w-full rounded-lg border-2 border-dashed border-base-content p-12 text-center"
|
||||
@click="newEntry()"
|
||||
class="border-base-content relative block w-full rounded-lg border-2 border-dashed p-12 text-center"
|
||||
@click="maintenanceEditModal?.openCreateModal(props.item.id)"
|
||||
>
|
||||
<MdiWrenchClock class="inline size-16" />
|
||||
<span class="mt-2 block text-sm font-medium text-gray-900"> Create Your First Entry </span>
|
||||
<span class="mt-2 block text-sm font-medium text-gray-900">
|
||||
{{ $t("maintenance.list.create_first") }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
<template>
|
||||
<BaseContainer class="mb-16">
|
||||
<BaseSectionHeader> Locations </BaseSectionHeader>
|
||||
<BaseSectionHeader> {{ $t("menu.locations") }} </BaseSectionHeader>
|
||||
<BaseCard>
|
||||
<div class="p-4">
|
||||
<div class="mb-2 flex justify-end">
|
||||
|
||||
137
frontend/pages/maintenance.vue
Normal file
137
frontend/pages/maintenance.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { StatsFormat } from "~~/components/global/StatCard/types";
|
||||
import { MaintenanceFilterStatus } from "~~/lib/api/types/data-contracts";
|
||||
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 MaintenanceEditModal from "~~/components/Maintenance/EditModal.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const api = useUserApi();
|
||||
|
||||
const maintenanceFilter = ref(MaintenanceFilterStatus.MaintenanceFilterStatusScheduled);
|
||||
const maintenanceEditModal = ref<InstanceType<typeof MaintenanceEditModal>>();
|
||||
|
||||
const { data: maintenanceData, refresh: refreshList } = useAsyncData(
|
||||
async () => {
|
||||
const { data } = await api.maintenance.getAll({ status: maintenanceFilter.value });
|
||||
console.log(data);
|
||||
return data;
|
||||
},
|
||||
{
|
||||
watch: [maintenanceFilter],
|
||||
}
|
||||
);
|
||||
|
||||
const stats = computed(() => {
|
||||
if (!maintenanceData.value) return [];
|
||||
|
||||
return [
|
||||
{
|
||||
id: "count",
|
||||
title: t("maintenance.total_entries"),
|
||||
value: maintenanceData.value ? maintenanceData.value.length || 0 : 0,
|
||||
type: "number" as StatsFormat,
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<BaseContainer class="mb-6 flex flex-col gap-8">
|
||||
<BaseSectionHeader> {{ $t("menu.maintenance") }} </BaseSectionHeader>
|
||||
<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">
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:class="`${maintenanceFilter == MaintenanceFilterStatus.MaintenanceFilterStatusScheduled ? 'btn-active' : ''}`"
|
||||
@click="maintenanceFilter = MaintenanceFilterStatus.MaintenanceFilterStatusScheduled"
|
||||
>
|
||||
{{ $t("maintenance.filter.scheduled") }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:class="`${maintenanceFilter == MaintenanceFilterStatus.MaintenanceFilterStatusCompleted ? 'btn-active' : ''}`"
|
||||
@click="maintenanceFilter = MaintenanceFilterStatus.MaintenanceFilterStatusCompleted"
|
||||
>
|
||||
{{ $t("maintenance.filter.completed") }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
:class="`${maintenanceFilter == MaintenanceFilterStatus.MaintenanceFilterStatusBoth ? 'btn-active' : ''}`"
|
||||
@click="maintenanceFilter = MaintenanceFilterStatus.MaintenanceFilterStatusBoth"
|
||||
>
|
||||
{{ $t("maintenance.filter.both") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<!-- begin -->
|
||||
<MaintenanceEditModal ref="maintenanceEditModal" @changed="refreshList"></MaintenanceEditModal>
|
||||
<div class="container space-y-6">
|
||||
<BaseCard v-for="e in maintenanceData" :key="e.id">
|
||||
<BaseSectionHeader class="border-b border-b-gray-300 p-6">
|
||||
<span class="text-base-content">
|
||||
<NuxtLink class="hover:underline" :to="`/item/${e.itemID}`">
|
||||
{{ e.itemName }}
|
||||
</NuxtLink>
|
||||
-
|
||||
{{ 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 justify-end gap-1 p-4">
|
||||
<BaseButton size="sm" @click="maintenanceEditModal?.openUpdateModal(e)">
|
||||
<template #icon>
|
||||
<MdiEdit />
|
||||
</template>
|
||||
{{ $t("maintenance.list.edit") }}
|
||||
</BaseButton>
|
||||
<BaseButton size="sm" @click="maintenanceEditModal?.deleteEntry(e.id)">
|
||||
<template #icon>
|
||||
<MdiDelete />
|
||||
</template>
|
||||
{{ $t("maintenance.list.delete") }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</BaseCard>
|
||||
</div>
|
||||
</section>
|
||||
</BaseContainer>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user