mirror of
https://github.com/hay-kot/homebox.git
synced 2024-11-16 21:58:40 +00:00
add support for custom text fields
This commit is contained in:
parent
57f9372e49
commit
434f1fa411
11 changed files with 384 additions and 38 deletions
|
@ -352,7 +352,7 @@ const docTemplate = `{
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Items"
|
"Items Attachments"
|
||||||
],
|
],
|
||||||
"summary": "imports items into the database",
|
"summary": "imports items into the database",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -415,7 +415,7 @@ const docTemplate = `{
|
||||||
"application/octet-stream"
|
"application/octet-stream"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Items"
|
"Items Attachments"
|
||||||
],
|
],
|
||||||
"summary": "retrieves an attachment for an item",
|
"summary": "retrieves an attachment for an item",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -452,7 +452,7 @@ const docTemplate = `{
|
||||||
"application/octet-stream"
|
"application/octet-stream"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Items"
|
"Items Attachments"
|
||||||
],
|
],
|
||||||
"summary": "retrieves an attachment for an item",
|
"summary": "retrieves an attachment for an item",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -487,7 +487,7 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Items"
|
"Items Attachments"
|
||||||
],
|
],
|
||||||
"summary": "retrieves an attachment for an item",
|
"summary": "retrieves an attachment for an item",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -531,7 +531,7 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Items"
|
"Items Attachments"
|
||||||
],
|
],
|
||||||
"summary": "retrieves an attachment for an item",
|
"summary": "retrieves an attachment for an item",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -1256,6 +1256,32 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"repo.ItemField": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"booleanValue": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"numberValue": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"textValue": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"timeValue": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repo.ItemOut": {
|
"repo.ItemOut": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -1271,6 +1297,13 @@ const docTemplate = `{
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"fields": {
|
||||||
|
"description": "Future",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/repo.ItemField"
|
||||||
|
}
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -1388,6 +1421,12 @@ const docTemplate = `{
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"fields": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/repo.ItemField"
|
||||||
|
}
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
@ -344,7 +344,7 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Items"
|
"Items Attachments"
|
||||||
],
|
],
|
||||||
"summary": "imports items into the database",
|
"summary": "imports items into the database",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -407,7 +407,7 @@
|
||||||
"application/octet-stream"
|
"application/octet-stream"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Items"
|
"Items Attachments"
|
||||||
],
|
],
|
||||||
"summary": "retrieves an attachment for an item",
|
"summary": "retrieves an attachment for an item",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -444,7 +444,7 @@
|
||||||
"application/octet-stream"
|
"application/octet-stream"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Items"
|
"Items Attachments"
|
||||||
],
|
],
|
||||||
"summary": "retrieves an attachment for an item",
|
"summary": "retrieves an attachment for an item",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -479,7 +479,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Items"
|
"Items Attachments"
|
||||||
],
|
],
|
||||||
"summary": "retrieves an attachment for an item",
|
"summary": "retrieves an attachment for an item",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -523,7 +523,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"Items"
|
"Items Attachments"
|
||||||
],
|
],
|
||||||
"summary": "retrieves an attachment for an item",
|
"summary": "retrieves an attachment for an item",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
@ -1248,6 +1248,32 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"repo.ItemField": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"booleanValue": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"numberValue": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"textValue": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"timeValue": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"repo.ItemOut": {
|
"repo.ItemOut": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -1263,6 +1289,13 @@
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"fields": {
|
||||||
|
"description": "Future",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/repo.ItemField"
|
||||||
|
}
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -1380,6 +1413,12 @@
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"fields": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/repo.ItemField"
|
||||||
|
}
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
@ -63,6 +63,23 @@ definitions:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
repo.ItemField:
|
||||||
|
properties:
|
||||||
|
booleanValue:
|
||||||
|
type: boolean
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
numberValue:
|
||||||
|
type: integer
|
||||||
|
textValue:
|
||||||
|
type: string
|
||||||
|
timeValue:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
repo.ItemOut:
|
repo.ItemOut:
|
||||||
properties:
|
properties:
|
||||||
attachments:
|
attachments:
|
||||||
|
@ -73,6 +90,11 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
|
fields:
|
||||||
|
description: Future
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/repo.ItemField'
|
||||||
|
type: array
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
insured:
|
insured:
|
||||||
|
@ -153,6 +175,10 @@ definitions:
|
||||||
properties:
|
properties:
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
|
fields:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/repo.ItemField'
|
||||||
|
type: array
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
insured:
|
insured:
|
||||||
|
@ -653,7 +679,7 @@ paths:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: imports items into the database
|
summary: imports items into the database
|
||||||
tags:
|
tags:
|
||||||
- Items
|
- Items Attachments
|
||||||
/v1/items/{id}/attachments/{attachment_id}:
|
/v1/items/{id}/attachments/{attachment_id}:
|
||||||
delete:
|
delete:
|
||||||
parameters:
|
parameters:
|
||||||
|
@ -674,7 +700,7 @@ paths:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: retrieves an attachment for an item
|
summary: retrieves an attachment for an item
|
||||||
tags:
|
tags:
|
||||||
- Items
|
- Items Attachments
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
- description: Item ID
|
- description: Item ID
|
||||||
|
@ -698,7 +724,7 @@ paths:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: retrieves an attachment for an item
|
summary: retrieves an attachment for an item
|
||||||
tags:
|
tags:
|
||||||
- Items
|
- Items Attachments
|
||||||
put:
|
put:
|
||||||
parameters:
|
parameters:
|
||||||
- description: Item ID
|
- description: Item ID
|
||||||
|
@ -726,7 +752,7 @@ paths:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: retrieves an attachment for an item
|
summary: retrieves an attachment for an item
|
||||||
tags:
|
tags:
|
||||||
- Items
|
- Items Attachments
|
||||||
/v1/items/{id}/attachments/download:
|
/v1/items/{id}/attachments/download:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
|
@ -749,7 +775,7 @@ paths:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: retrieves an attachment for an item
|
summary: retrieves an attachment for an item
|
||||||
tags:
|
tags:
|
||||||
- Items
|
- Items Attachments
|
||||||
/v1/items/import:
|
/v1/items/import:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
|
|
|
@ -22,7 +22,7 @@ type (
|
||||||
|
|
||||||
// HandleItemsImport godocs
|
// HandleItemsImport godocs
|
||||||
// @Summary imports items into the database
|
// @Summary imports items into the database
|
||||||
// @Tags Items
|
// @Tags Items Attachments
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param file formData file true "File attachment"
|
// @Param file formData file true "File attachment"
|
||||||
|
@ -99,7 +99,7 @@ func (ctrl *V1Controller) HandleItemAttachmentCreate() http.HandlerFunc {
|
||||||
|
|
||||||
// HandleItemAttachmentGet godocs
|
// HandleItemAttachmentGet godocs
|
||||||
// @Summary retrieves an attachment for an item
|
// @Summary retrieves an attachment for an item
|
||||||
// @Tags Items
|
// @Tags Items Attachments
|
||||||
// @Produce application/octet-stream
|
// @Produce application/octet-stream
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param token query string true "Attachment token"
|
// @Param token query string true "Attachment token"
|
||||||
|
@ -126,7 +126,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc {
|
||||||
|
|
||||||
// HandleItemAttachmentToken godocs
|
// HandleItemAttachmentToken godocs
|
||||||
// @Summary retrieves an attachment for an item
|
// @Summary retrieves an attachment for an item
|
||||||
// @Tags Items
|
// @Tags Items Attachments
|
||||||
// @Produce application/octet-stream
|
// @Produce application/octet-stream
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param attachment_id path string true "Attachment ID"
|
// @Param attachment_id path string true "Attachment ID"
|
||||||
|
@ -139,7 +139,7 @@ func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc {
|
||||||
|
|
||||||
// HandleItemAttachmentDelete godocs
|
// HandleItemAttachmentDelete godocs
|
||||||
// @Summary retrieves an attachment for an item
|
// @Summary retrieves an attachment for an item
|
||||||
// @Tags Items
|
// @Tags Items Attachments
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param attachment_id path string true "Attachment ID"
|
// @Param attachment_id path string true "Attachment ID"
|
||||||
// @Success 204
|
// @Success 204
|
||||||
|
@ -151,7 +151,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc {
|
||||||
|
|
||||||
// HandleItemAttachmentUpdate godocs
|
// HandleItemAttachmentUpdate godocs
|
||||||
// @Summary retrieves an attachment for an item
|
// @Summary retrieves an attachment for an item
|
||||||
// @Tags Items
|
// @Tags Items Attachments
|
||||||
// @Param id path string true "Item ID"
|
// @Param id path string true "Item ID"
|
||||||
// @Param attachment_id path string true "Attachment ID"
|
// @Param attachment_id path string true "Attachment ID"
|
||||||
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
|
// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update"
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/hay-kot/homebox/backend/ent"
|
"github.com/hay-kot/homebox/backend/ent"
|
||||||
"github.com/hay-kot/homebox/backend/ent/group"
|
"github.com/hay-kot/homebox/backend/ent/group"
|
||||||
"github.com/hay-kot/homebox/backend/ent/item"
|
"github.com/hay-kot/homebox/backend/ent/item"
|
||||||
|
"github.com/hay-kot/homebox/backend/ent/itemfield"
|
||||||
"github.com/hay-kot/homebox/backend/ent/label"
|
"github.com/hay-kot/homebox/backend/ent/label"
|
||||||
"github.com/hay-kot/homebox/backend/ent/location"
|
"github.com/hay-kot/homebox/backend/ent/location"
|
||||||
"github.com/hay-kot/homebox/backend/ent/predicate"
|
"github.com/hay-kot/homebox/backend/ent/predicate"
|
||||||
|
@ -27,6 +28,16 @@ type (
|
||||||
SortBy string `json:"sortBy"`
|
SortBy string `json:"sortBy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ItemField struct {
|
||||||
|
ID uuid.UUID `json:"id,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
TextValue string `json:"textValue"`
|
||||||
|
NumberValue int `json:"numberValue"`
|
||||||
|
BooleanValue bool `json:"booleanValue"`
|
||||||
|
TimeValue time.Time `json:"timeValue,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
ItemCreate struct {
|
ItemCreate struct {
|
||||||
ImportRef string `json:"-"`
|
ImportRef string `json:"-"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -69,8 +80,8 @@ type (
|
||||||
SoldNotes string `json:"soldNotes"`
|
SoldNotes string `json:"soldNotes"`
|
||||||
|
|
||||||
// Extras
|
// Extras
|
||||||
Notes string `json:"notes"`
|
Notes string `json:"notes"`
|
||||||
// Fields []*FieldSummary `json:"fields"`
|
Fields []ItemField `json:"fields"`
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemSummary struct {
|
ItemSummary struct {
|
||||||
|
@ -116,7 +127,7 @@ type (
|
||||||
|
|
||||||
Attachments []ItemAttachment `json:"attachments"`
|
Attachments []ItemAttachment `json:"attachments"`
|
||||||
// Future
|
// Future
|
||||||
// Fields []*FieldSummary `json:"fields"`
|
Fields []ItemField `json:"fields"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -156,12 +167,33 @@ var (
|
||||||
mapItemOutErr = mapTErrFunc(mapItemOut)
|
mapItemOutErr = mapTErrFunc(mapItemOut)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func mapFields(fields []*ent.ItemField) []ItemField {
|
||||||
|
result := make([]ItemField, len(fields))
|
||||||
|
for i, f := range fields {
|
||||||
|
result[i] = ItemField{
|
||||||
|
ID: f.ID,
|
||||||
|
Type: f.Type.String(),
|
||||||
|
Name: f.Name,
|
||||||
|
TextValue: f.TextValue,
|
||||||
|
NumberValue: f.NumberValue,
|
||||||
|
BooleanValue: f.BooleanValue,
|
||||||
|
TimeValue: f.TimeValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func mapItemOut(item *ent.Item) ItemOut {
|
func mapItemOut(item *ent.Item) ItemOut {
|
||||||
var attachments []ItemAttachment
|
var attachments []ItemAttachment
|
||||||
if item.Edges.Attachments != nil {
|
if item.Edges.Attachments != nil {
|
||||||
attachments = mapEach(item.Edges.Attachments, ToItemAttachment)
|
attachments = mapEach(item.Edges.Attachments, ToItemAttachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fields []ItemField
|
||||||
|
if item.Edges.Fields != nil {
|
||||||
|
fields = mapFields(item.Edges.Fields)
|
||||||
|
}
|
||||||
|
|
||||||
return ItemOut{
|
return ItemOut{
|
||||||
ItemSummary: mapItemSummary(item),
|
ItemSummary: mapItemSummary(item),
|
||||||
LifetimeWarranty: item.LifetimeWarranty,
|
LifetimeWarranty: item.LifetimeWarranty,
|
||||||
|
@ -187,6 +219,7 @@ func mapItemOut(item *ent.Item) ItemOut {
|
||||||
// Extras
|
// Extras
|
||||||
Notes: item.Notes,
|
Notes: item.Notes,
|
||||||
Attachments: attachments,
|
Attachments: attachments,
|
||||||
|
Fields: fields,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,5 +403,63 @@ func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data
|
||||||
return ItemOut{}, err
|
return ItemOut{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fields, err := e.db.ItemField.Query().Where(itemfield.HasItemWith(item.ID(data.ID))).All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return ItemOut{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldIds := newIDSet(fields)
|
||||||
|
|
||||||
|
// Update Existing Fields
|
||||||
|
for _, f := range data.Fields {
|
||||||
|
if f.ID == uuid.Nil {
|
||||||
|
// Create New Field
|
||||||
|
_, err = e.db.ItemField.Create().
|
||||||
|
SetItemID(data.ID).
|
||||||
|
SetType(itemfield.Type(f.Type)).
|
||||||
|
SetName(f.Name).
|
||||||
|
SetTextValue(f.TextValue).
|
||||||
|
SetNumberValue(f.NumberValue).
|
||||||
|
SetBooleanValue(f.BooleanValue).
|
||||||
|
SetTimeValue(f.TimeValue).
|
||||||
|
Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return ItemOut{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := e.db.ItemField.Update().
|
||||||
|
Where(
|
||||||
|
itemfield.ID(f.ID),
|
||||||
|
itemfield.HasItemWith(item.ID(data.ID)),
|
||||||
|
).
|
||||||
|
SetType(itemfield.Type(f.Type)).
|
||||||
|
SetName(f.Name).
|
||||||
|
SetTextValue(f.TextValue).
|
||||||
|
SetNumberValue(f.NumberValue).
|
||||||
|
SetBooleanValue(f.BooleanValue).
|
||||||
|
SetTimeValue(f.TimeValue)
|
||||||
|
|
||||||
|
_, err = opt.Save(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return ItemOut{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldIds.Remove(f.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete Fields that are no longer present
|
||||||
|
if fieldIds.Len() > 0 {
|
||||||
|
_, err = e.db.ItemField.Delete().
|
||||||
|
Where(
|
||||||
|
itemfield.IDIn(fieldIds.Slice()...),
|
||||||
|
itemfield.HasItemWith(item.ID(data.ID)),
|
||||||
|
).Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return ItemOut{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return e.GetOne(ctx, data.ID)
|
return e.GetOne(ctx, data.ID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,17 +50,40 @@
|
||||||
const selectedIdx = ref(-1);
|
const selectedIdx = ref(-1);
|
||||||
|
|
||||||
const internalSelected = useVModel(props, "modelValue", emit);
|
const internalSelected = useVModel(props, "modelValue", emit);
|
||||||
|
const internalValue = useVModel(props, "value", emit);
|
||||||
|
|
||||||
watch(selectedIdx, newVal => {
|
watch(selectedIdx, newVal => {
|
||||||
internalSelected.value = props.items[newVal];
|
internalSelected.value = props.items[newVal];
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(internalSelected, newVal => {
|
watch(selectedIdx, newVal => {
|
||||||
if (props.valueKey) {
|
if (props.valueKey) {
|
||||||
emit("update:value", newVal[props.valueKey]);
|
internalValue.value = props.items[newVal][props.valueKey];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
internalSelected,
|
||||||
|
() => {
|
||||||
|
const idx = props.items.findIndex(item => compare(item, internalSelected.value));
|
||||||
|
selectedIdx.value = idx;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
internalValue,
|
||||||
|
() => {
|
||||||
|
const idx = props.items.findIndex(item => compare(item[props.valueKey], internalValue.value));
|
||||||
|
selectedIdx.value = idx;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
function compare(a: any, b: any): boolean {
|
function compare(a: any, b: any): boolean {
|
||||||
if (a === b) {
|
if (a === b) {
|
||||||
|
@ -73,15 +96,4 @@
|
||||||
|
|
||||||
return JSON.stringify(a) === JSON.stringify(b);
|
return JSON.stringify(a) === JSON.stringify(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
|
||||||
internalSelected,
|
|
||||||
() => {
|
|
||||||
const idx = props.items.findIndex(item => compare(item, internalSelected.value));
|
|
||||||
selectedIdx.value = idx;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,11 +2,23 @@ import { faker } from "@faker-js/faker";
|
||||||
import { expect } from "vitest";
|
import { expect } from "vitest";
|
||||||
import { overrideParts } from "../../base/urls";
|
import { overrideParts } from "../../base/urls";
|
||||||
import { PublicApi } from "../../public";
|
import { PublicApi } from "../../public";
|
||||||
import { LabelCreate, LocationCreate, UserRegistration } from "../../types/data-contracts";
|
import { ItemField, LabelCreate, LocationCreate, UserRegistration } from "../../types/data-contracts";
|
||||||
import * as config from "../../../../test/config";
|
import * as config from "../../../../test/config";
|
||||||
import { UserClient } from "../../user";
|
import { UserClient } from "../../user";
|
||||||
import { Requests } from "../../../requests";
|
import { Requests } from "../../../requests";
|
||||||
|
|
||||||
|
function itemField(id = null): ItemField {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: faker.lorem.word(),
|
||||||
|
type: "text",
|
||||||
|
textValue: faker.lorem.sentence(),
|
||||||
|
booleanValue: false,
|
||||||
|
numberValue: faker.datatype.number(),
|
||||||
|
timeValue: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a random user registration object that can be
|
* Returns a random user registration object that can be
|
||||||
* used to signup a new user.
|
* used to signup a new user.
|
||||||
|
@ -72,6 +84,7 @@ export const factories = {
|
||||||
user,
|
user,
|
||||||
location,
|
location,
|
||||||
label,
|
label,
|
||||||
|
itemField,
|
||||||
client: {
|
client: {
|
||||||
public: publicClient,
|
public: publicClient,
|
||||||
user: userClient,
|
user: userClient,
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
import { describe, test, expect } from "vitest";
|
import { describe, test, expect } from "vitest";
|
||||||
import { LocationOut } from "../../types/data-contracts";
|
import { ItemField, LocationOut } from "../../types/data-contracts";
|
||||||
import { AttachmentTypes } from "../../types/non-generated";
|
import { AttachmentTypes } from "../../types/non-generated";
|
||||||
import { UserClient } from "../../user";
|
import { UserClient } from "../../user";
|
||||||
|
import { factories } from "../factories";
|
||||||
import { sharedUserClient } from "../test-utils";
|
import { sharedUserClient } from "../test-utils";
|
||||||
|
|
||||||
describe("user should be able to create an item and add an attachment", () => {
|
describe("user should be able to create an item and add an attachment", () => {
|
||||||
|
@ -58,4 +60,57 @@ describe("user should be able to create an item and add an attachment", () => {
|
||||||
api.items.delete(item.id);
|
api.items.delete(item.id);
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("user should be able to create and delete fields on an item", async () => {
|
||||||
|
const api = await sharedUserClient();
|
||||||
|
const [location, cleanup] = await useLocation(api);
|
||||||
|
|
||||||
|
const { response, data: item } = await api.items.create({
|
||||||
|
name: faker.vehicle.model(),
|
||||||
|
labelIds: [],
|
||||||
|
description: faker.lorem.paragraph(1),
|
||||||
|
locationId: location.id,
|
||||||
|
});
|
||||||
|
expect(response.status).toBe(201);
|
||||||
|
|
||||||
|
const fields: ItemField[] = [
|
||||||
|
factories.itemField(),
|
||||||
|
factories.itemField(),
|
||||||
|
factories.itemField(),
|
||||||
|
factories.itemField(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add fields
|
||||||
|
const itemUpdate = {
|
||||||
|
...item,
|
||||||
|
locationId: item.location.id,
|
||||||
|
labelIds: item.labels.map(l => l.id),
|
||||||
|
fields,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { response: updateResponse, data: item2 } = await api.items.update(item.id, itemUpdate);
|
||||||
|
expect(updateResponse.status).toBe(200);
|
||||||
|
|
||||||
|
expect(item2.fields).toHaveLength(fields.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
expect(item2.fields[i].name).toBe(fields[i].name);
|
||||||
|
expect(item2.fields[i].textValue).toBe(fields[i].textValue);
|
||||||
|
expect(item2.fields[i].numberValue).toBe(fields[i].numberValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
itemUpdate.fields = [fields[0], fields[1]];
|
||||||
|
|
||||||
|
const { response: updateResponse2, data: item3 } = await api.items.update(item.id, itemUpdate);
|
||||||
|
expect(updateResponse2.status).toBe(200);
|
||||||
|
|
||||||
|
expect(item3.fields).toHaveLength(2);
|
||||||
|
for (let i = 0; i < item3.fields.length; i++) {
|
||||||
|
expect(item3.fields[i].name).toBe(itemUpdate.fields[i].name);
|
||||||
|
expect(item3.fields[i].textValue).toBe(itemUpdate.fields[i].textValue);
|
||||||
|
expect(item3.fields[i].numberValue).toBe(itemUpdate.fields[i].numberValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -51,10 +51,23 @@ export interface ItemCreate {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ItemField {
|
||||||
|
booleanValue: boolean;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
numberValue: number;
|
||||||
|
textValue: string;
|
||||||
|
timeValue: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ItemOut {
|
export interface ItemOut {
|
||||||
attachments: ItemAttachment[];
|
attachments: ItemAttachment[];
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
|
/** Future */
|
||||||
|
fields: ItemField[];
|
||||||
id: string;
|
id: string;
|
||||||
insured: boolean;
|
insured: boolean;
|
||||||
labels: LabelSummary[];
|
labels: LabelSummary[];
|
||||||
|
@ -108,6 +121,7 @@ export interface ItemSummary {
|
||||||
|
|
||||||
export interface ItemUpdate {
|
export interface ItemUpdate {
|
||||||
description: string;
|
description: string;
|
||||||
|
fields: ItemField[];
|
||||||
id: string;
|
id: string;
|
||||||
insured: boolean;
|
insured: boolean;
|
||||||
labelIds: string[];
|
labelIds: string[];
|
||||||
|
|
|
@ -278,6 +278,34 @@
|
||||||
|
|
||||||
toast.success("Attachment updated");
|
toast.success("Attachment updated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom Fields
|
||||||
|
// const fieldTypes = [
|
||||||
|
// {
|
||||||
|
// name: "Text",
|
||||||
|
// value: "text",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Number",
|
||||||
|
// value: "number",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "Boolean",
|
||||||
|
// value: "boolean",
|
||||||
|
// },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
function addField() {
|
||||||
|
item.value.fields.push({
|
||||||
|
id: null,
|
||||||
|
name: "Field Name",
|
||||||
|
type: "text",
|
||||||
|
textValue: "",
|
||||||
|
numberValue: 0,
|
||||||
|
booleanValue: false,
|
||||||
|
timeValue: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -364,6 +392,31 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<BaseCard>
|
||||||
|
<template #title> Custom Fields </template>
|
||||||
|
<div class="px-5 divide-y divide-gray-300 space-y-4">
|
||||||
|
<div
|
||||||
|
v-for="(field, idx) in item.fields"
|
||||||
|
:key="`field-${idx}`"
|
||||||
|
class="grid grid-cols-2 md:grid-cols-4 gap-2"
|
||||||
|
>
|
||||||
|
<!-- <FormSelect v-model:value="field.type" label="Field Type" :items="fieldTypes" value-key="value" /> -->
|
||||||
|
<FormTextField v-model="field.name" label="Name" />
|
||||||
|
<div class="flex items-end col-span-3">
|
||||||
|
<FormTextField v-model="field.textValue" label="Value" />
|
||||||
|
<div class="tooltip" data-tip="Delete">
|
||||||
|
<button class="btn btn-sm btn-square mb-2 ml-2" @click="item.fields.splice(idx, 1)">
|
||||||
|
<Icon name="mdi-delete" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-5 pb-4 mt-4 flex justify-end">
|
||||||
|
<BaseButton size="sm" @click="addField"> Add </BaseButton>
|
||||||
|
</div>
|
||||||
|
</BaseCard>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="!preferences.editorSimpleView"
|
v-if="!preferences.editorSimpleView"
|
||||||
ref="attDropZone"
|
ref="attDropZone"
|
||||||
|
|
|
@ -96,6 +96,10 @@
|
||||||
name: "Notes",
|
name: "Notes",
|
||||||
text: item.value?.notes,
|
text: item.value?.notes,
|
||||||
},
|
},
|
||||||
|
...item.value.fields.map(field => ({
|
||||||
|
name: field.name,
|
||||||
|
text: field.textValue,
|
||||||
|
})),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue