initial commit
This commit is contained in:
216
internal/ui/assets/assets.go
Normal file
216
internal/ui/assets/assets.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"git.brettb.xyz/goinv/client/internal/api"
|
||||
"git.brettb.xyz/goinv/client/internal/types"
|
||||
"git.brettb.xyz/goinv/client/internal/ui/dialogs"
|
||||
"git.brettb.xyz/goinv/client/internal/ui/style"
|
||||
"git.brettb.xyz/goinv/client/internal/ui/utils"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
status_CONFIRM_DELETE_ASSET = "delete_asset"
|
||||
)
|
||||
|
||||
type Assets struct {
|
||||
*tview.Box
|
||||
client *api.APIClient
|
||||
title string
|
||||
logger *zap.Logger
|
||||
assetTable *tview.Table
|
||||
assetList assetListReport
|
||||
shelfLocationCache shelfListReport
|
||||
assetTableHeaders []string
|
||||
assetTableExpansions []int
|
||||
cmdDialog *dialogs.CommandDialog
|
||||
confirmDialog *dialogs.ConfirmDialog
|
||||
errorDialog *dialogs.ErrorDialog
|
||||
progressDialog *dialogs.ProgressDialog
|
||||
messageDialog *dialogs.MessageDialog
|
||||
allDialogs []dialogs.Dialog
|
||||
confirmData string
|
||||
assetListFunc func() ([]types.Asset, error)
|
||||
shelfListFunc func() (map[uint64]types.ShelfLocation, error)
|
||||
}
|
||||
|
||||
type assetSelectedItem struct {
|
||||
id string
|
||||
item string
|
||||
quantity string
|
||||
shelfLocation string
|
||||
manufacturer string
|
||||
model string
|
||||
category string
|
||||
}
|
||||
|
||||
type assetListReport struct {
|
||||
mu sync.Mutex
|
||||
report []types.Asset
|
||||
dirty bool
|
||||
}
|
||||
|
||||
type shelfListReport struct {
|
||||
mu sync.Mutex
|
||||
report map[uint64]types.ShelfLocation
|
||||
}
|
||||
|
||||
func NewAssets(logger *zap.Logger, client *api.APIClient) *Assets {
|
||||
assets := &Assets{
|
||||
Box: tview.NewBox(),
|
||||
client: client,
|
||||
title: "assets",
|
||||
logger: logger,
|
||||
assetTable: tview.NewTable(),
|
||||
assetTableHeaders: []string{"id", "item", "quantity", "shelf location", "manufacturer", "model", "category"},
|
||||
assetTableExpansions: []int{1, 4, 1, 2, 2, 2, 2},
|
||||
confirmDialog: dialogs.NewConfirmDialog(logger),
|
||||
errorDialog: dialogs.NewErrorDialog(logger),
|
||||
progressDialog: dialogs.NewProgressDialog(logger),
|
||||
messageDialog: dialogs.NewMessageDialog(logger, ""),
|
||||
}
|
||||
|
||||
assets.assetTable.SetBackgroundColor(style.BgColor)
|
||||
assets.assetTable.SetBorder(true)
|
||||
assets.updateAssetTableTitle(0)
|
||||
assets.assetTable.SetTitleColor(style.FgColor)
|
||||
assets.assetTable.SetBorderColor(style.BorderColor)
|
||||
assets.assetTable.SetFixed(1, 1)
|
||||
assets.assetTable.SetSelectable(true, false)
|
||||
|
||||
assets.writeTableHeaders()
|
||||
|
||||
assets.assetTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if assets.assetTable.GetRowCount() <= 1 {
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
assets.cmdDialog = dialogs.NewCommandDialog(logger, [][]string{
|
||||
{"create asset", "create a new asset"},
|
||||
{"view asset", "view the selected asset"},
|
||||
{"delete asset", "delete the selected asset"},
|
||||
{"refresh", "refresh the page"},
|
||||
})
|
||||
|
||||
assets.cmdDialog.SetSelectedFunc(func() {
|
||||
assets.cmdDialog.Hide()
|
||||
assets.runCommand(assets.cmdDialog.GetSelectedItem())
|
||||
}).SetCancelFunc(func() {
|
||||
assets.cmdDialog.Hide()
|
||||
})
|
||||
|
||||
assets.confirmDialog.SetSelectedFunc(func() {
|
||||
assets.confirmDialog.Hide()
|
||||
switch assets.confirmData {
|
||||
case status_CONFIRM_DELETE_ASSET:
|
||||
assets.delete()
|
||||
}
|
||||
}).SetCancelFunc(func() {
|
||||
assets.confirmDialog.Hide()
|
||||
})
|
||||
|
||||
assets.messageDialog.SetCancelFunc(func() {
|
||||
assets.messageDialog.Hide()
|
||||
})
|
||||
|
||||
assets.SetAssetListFunc(func() ([]types.Asset, error) {
|
||||
if asp, err := assets.client.RetrieveAllAssets(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
var aso []types.Asset
|
||||
|
||||
for _, a := range asp {
|
||||
aso = append(aso, *a)
|
||||
}
|
||||
|
||||
return aso, nil
|
||||
}
|
||||
})
|
||||
|
||||
assets.SetShelfListFunc(func() (map[uint64]types.ShelfLocation, error) {
|
||||
if resp, err := assets.client.RetrieveAllShelves(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
shelves := map[uint64]types.ShelfLocation{}
|
||||
|
||||
for _, a := range resp {
|
||||
shelves[a.ID] = *a
|
||||
}
|
||||
|
||||
return shelves, nil
|
||||
}
|
||||
})
|
||||
|
||||
assets.allDialogs = []dialogs.Dialog{
|
||||
assets.errorDialog,
|
||||
assets.messageDialog,
|
||||
assets.progressDialog,
|
||||
assets.confirmDialog,
|
||||
assets.cmdDialog,
|
||||
}
|
||||
|
||||
return assets
|
||||
}
|
||||
|
||||
func (a *Assets) GetTitle() string {
|
||||
return a.title
|
||||
}
|
||||
|
||||
func (a *Assets) HasFocus() bool {
|
||||
return dialogs.CheckDialogFocus(a.allDialogs...) || utils.CheckFocus(a.assetTable, a.Box)
|
||||
}
|
||||
|
||||
func (a *Assets) SubDialogHasFocus() bool {
|
||||
return dialogs.CheckDialogFocus(a.allDialogs...)
|
||||
}
|
||||
|
||||
func (a *Assets) Focus(delegate func(tview.Primitive)) {
|
||||
|
||||
for _, dialog := range a.allDialogs {
|
||||
if dialog.IsDisplay() {
|
||||
delegate(dialog)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
delegate(a.assetTable)
|
||||
}
|
||||
|
||||
func (a *Assets) SetAssetListFunc(list func() ([]types.Asset, error)) {
|
||||
a.assetListFunc = list
|
||||
}
|
||||
|
||||
func (a *Assets) SetShelfListFunc(list func() (map[uint64]types.ShelfLocation, error)) {
|
||||
a.shelfListFunc = list
|
||||
}
|
||||
|
||||
func (a *Assets) hideAllDialogs() {
|
||||
for _, dialog := range a.allDialogs {
|
||||
dialog.Hide()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Assets) getSelectedItem() *assetSelectedItem {
|
||||
selectedItem := assetSelectedItem{}
|
||||
|
||||
if a.assetTable.GetRowCount() <= 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
row, _ := a.assetTable.GetSelection()
|
||||
selectedItem.id = a.assetTable.GetCell(row, 0).Text
|
||||
selectedItem.item = a.assetTable.GetCell(row, 1).Text
|
||||
selectedItem.quantity = a.assetTable.GetCell(row, 2).Text
|
||||
selectedItem.shelfLocation = a.assetTable.GetCell(row, 3).Text
|
||||
selectedItem.manufacturer = a.assetTable.GetCell(row, 4).Text
|
||||
selectedItem.model = a.assetTable.GetCell(row, 5).Text
|
||||
selectedItem.category = a.assetTable.GetCell(row, 6).Text
|
||||
|
||||
return &selectedItem
|
||||
}
|
||||
103
internal/ui/assets/command.go
Normal file
103
internal/ui/assets/command.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.brettb.xyz/goinv/client/internal/ui/dialogs"
|
||||
"git.brettb.xyz/goinv/client/internal/ui/style"
|
||||
)
|
||||
|
||||
func (a *Assets) runCommand(cmd string) {
|
||||
switch cmd {
|
||||
case "create asset", "view asset":
|
||||
a.cNotImplemented()
|
||||
return
|
||||
case "delete asset":
|
||||
a.cdelete()
|
||||
return
|
||||
case "refresh":
|
||||
a.crefresh()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Assets) cNotImplemented() {
|
||||
a.displayError("not implemented", fmt.Errorf("this command has not been implemented"))
|
||||
}
|
||||
|
||||
// Confirm deletion
|
||||
func (a *Assets) cdelete() {
|
||||
selectedItem := a.getSelectedItem()
|
||||
|
||||
// Empty table
|
||||
if selectedItem == nil {
|
||||
a.displayError("DELETE ASSET ERROR", fmt.Errorf("no assets to delete"))
|
||||
return
|
||||
}
|
||||
|
||||
title := "delete asset"
|
||||
a.confirmDialog.SetTitle(title)
|
||||
a.confirmData = status_CONFIRM_DELETE_ASSET
|
||||
bgColor := style.GetColorHex(style.DialogBorderColor)
|
||||
fgColor := style.GetColorHex(style.DialogFgColor)
|
||||
|
||||
assetName := fmt.Sprintf("[%s:%s:b]ASSET NAME:[:-:-] %s", fgColor, bgColor, selectedItem.item)
|
||||
assetQuantity := fmt.Sprintf(" [%s:%s:b]QUANTITY:[:-:-] %s", fgColor, bgColor, selectedItem.quantity)
|
||||
|
||||
confirmMsg := fmt.Sprintf("%s\n%s\nAre you sure you want to delete the selected asset ?", assetName, assetQuantity)
|
||||
a.confirmDialog.SetText(confirmMsg)
|
||||
a.confirmDialog.Display()
|
||||
}
|
||||
|
||||
func (a *Assets) delete() {
|
||||
selectedItem := a.getSelectedItem()
|
||||
|
||||
a.progressDialog.SetTitle(fmt.Sprintf("deleting asset %s", selectedItem.id))
|
||||
a.progressDialog.Display()
|
||||
|
||||
del := func() {
|
||||
_, err := a.client.DeleteAssetByID(selectedItem.id)
|
||||
|
||||
a.progressDialog.Hide()
|
||||
|
||||
if err != nil {
|
||||
a.displayError("DELETE ASSET ERROR", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// display success message
|
||||
a.messageDialog.SetTitle(fmt.Sprintf("deleting asset %s", selectedItem.id))
|
||||
a.messageDialog.SetText(dialogs.MessageGeneric, "Success!", fmt.Sprintf("Asset %s successfully deleted.", selectedItem.id))
|
||||
a.messageDialog.Display()
|
||||
|
||||
a.UpdateAssetData()
|
||||
}
|
||||
|
||||
del()
|
||||
}
|
||||
|
||||
func (a *Assets) crefresh() {
|
||||
a.progressDialog.SetTitle("refreshing assets")
|
||||
a.progressDialog.Display()
|
||||
|
||||
ref := func() {
|
||||
a.UpdateShelfData()
|
||||
a.UpdateAssetData()
|
||||
|
||||
a.progressDialog.Hide()
|
||||
|
||||
if !a.errorDialog.IsDisplay() {
|
||||
a.messageDialog.SetTitle(fmt.Sprintf("asset refresh"))
|
||||
a.messageDialog.SetText(dialogs.MessageGeneric, "Refreshed!", "Successfully refreshed page.")
|
||||
a.messageDialog.Display()
|
||||
}
|
||||
}
|
||||
|
||||
ref()
|
||||
}
|
||||
|
||||
func (a *Assets) displayError(title string, err error) {
|
||||
a.errorDialog.SetTitle(title)
|
||||
a.errorDialog.SetText(fmt.Sprintf("%v", err))
|
||||
a.errorDialog.Display()
|
||||
}
|
||||
48
internal/ui/assets/data.go
Normal file
48
internal/ui/assets/data.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package assets
|
||||
|
||||
import "git.brettb.xyz/goinv/client/internal/types"
|
||||
|
||||
func (a *Assets) UpdateData() {
|
||||
a.UpdateShelfData()
|
||||
a.UpdateAssetData()
|
||||
}
|
||||
|
||||
func (a *Assets) UpdateAssetData() {
|
||||
assets, err := a.assetListFunc()
|
||||
a.assetList.mu.Lock()
|
||||
defer a.assetList.mu.Unlock()
|
||||
if err != nil {
|
||||
a.displayError("could not retrieve assets", err)
|
||||
a.assetList.dirty = true
|
||||
return
|
||||
}
|
||||
a.assetList.dirty = false
|
||||
a.assetList.report = assets
|
||||
}
|
||||
|
||||
func (a *Assets) UpdateShelfData() {
|
||||
shelves, err := a.shelfListFunc()
|
||||
if err != nil {
|
||||
a.displayError("could not retrieve shelves", err)
|
||||
return
|
||||
}
|
||||
a.shelfLocationCache.mu.Lock()
|
||||
a.shelfLocationCache.report = shelves
|
||||
a.shelfLocationCache.mu.Unlock()
|
||||
}
|
||||
|
||||
func (a *Assets) getAssetData() []types.Asset {
|
||||
a.assetList.mu.Lock()
|
||||
assetReport := a.assetList.report
|
||||
defer a.assetList.mu.Unlock()
|
||||
|
||||
return assetReport
|
||||
}
|
||||
|
||||
func (a *Assets) getShelfData() map[uint64]types.ShelfLocation {
|
||||
a.shelfLocationCache.mu.Lock()
|
||||
shelfReport := a.shelfLocationCache.report
|
||||
defer a.shelfLocationCache.mu.Unlock()
|
||||
|
||||
return shelfReport
|
||||
}
|
||||
24
internal/ui/assets/draw.go
Normal file
24
internal/ui/assets/draw.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package assets
|
||||
|
||||
import "github.com/gdamore/tcell/v2"
|
||||
|
||||
func (a *Assets) Draw(screen tcell.Screen) {
|
||||
a.refresh()
|
||||
a.Box.DrawForSubclass(screen, a)
|
||||
|
||||
x, y, width, height := a.GetInnerRect()
|
||||
|
||||
a.assetTable.SetRect(x, y, width, height)
|
||||
a.assetTable.Draw(screen)
|
||||
|
||||
x, y, width, height = a.assetTable.GetInnerRect()
|
||||
|
||||
for _, diag := range a.allDialogs {
|
||||
if diag.IsDisplay() {
|
||||
diag.SetRect(x, y, width, height)
|
||||
diag.Draw(screen)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
54
internal/ui/assets/key.go
Normal file
54
internal/ui/assets/key.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"git.brettb.xyz/goinv/client/internal/ui/utils"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
func (a *Assets) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) {
|
||||
return a.WrapInputHandler(func(event *tcell.EventKey, setFocus func(tview.Primitive)) {
|
||||
a.logger.Sugar().Debugf("assets event %v received", event)
|
||||
|
||||
if a.progressDialog.IsDisplay() {
|
||||
setFocus(a.progressDialog)
|
||||
return
|
||||
}
|
||||
|
||||
if a.errorDialog.HasFocus() {
|
||||
if errorHandler := a.errorDialog.InputHandler(); errorHandler != nil {
|
||||
errorHandler(event, setFocus)
|
||||
}
|
||||
}
|
||||
|
||||
if a.messageDialog.HasFocus() {
|
||||
if messageHandler := a.messageDialog.InputHandler(); messageHandler != nil {
|
||||
messageHandler(event, setFocus)
|
||||
}
|
||||
}
|
||||
|
||||
if a.confirmDialog.HasFocus() {
|
||||
if confirmHandler := a.confirmDialog.InputHandler(); confirmHandler != nil {
|
||||
confirmHandler(event, setFocus)
|
||||
}
|
||||
}
|
||||
|
||||
if a.cmdDialog.HasFocus() {
|
||||
if cmdHandler := a.cmdDialog.InputHandler(); cmdHandler != nil {
|
||||
cmdHandler(event, setFocus)
|
||||
}
|
||||
}
|
||||
|
||||
if a.assetTable.HasFocus() {
|
||||
if event.Rune() == utils.CommandMenuKey.Rune() {
|
||||
a.cmdDialog.Display()
|
||||
} else {
|
||||
if tableHandler := a.assetTable.InputHandler(); tableHandler != nil {
|
||||
tableHandler(event, setFocus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setFocus(a)
|
||||
})
|
||||
}
|
||||
97
internal/ui/assets/refresh.go
Normal file
97
internal/ui/assets/refresh.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.brettb.xyz/goinv/client/internal/ui/style"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
const tableHeaderOffset = 1
|
||||
|
||||
func (a *Assets) refresh() {
|
||||
assets := a.getAssetData()
|
||||
a.assetTable.Clear()
|
||||
a.updateAssetTableTitle(len(assets))
|
||||
|
||||
a.writeTableHeaders()
|
||||
|
||||
for i, asset := range assets {
|
||||
row := i + tableHeaderOffset
|
||||
|
||||
a.assetTable.SetCell(row, 0,
|
||||
tview.NewTableCell(fmt.Sprintf("%d", asset.ID)).
|
||||
SetExpansion(a.assetTableExpansions[0]).
|
||||
SetAlign(tview.AlignLeft))
|
||||
|
||||
a.assetTable.SetCell(row, 1,
|
||||
tview.NewTableCell(asset.Name).
|
||||
SetExpansion(a.assetTableExpansions[1]).
|
||||
SetAlign(tview.AlignLeft))
|
||||
|
||||
quantity := ""
|
||||
if asset.Quantity < 0 {
|
||||
quantity = "DNI"
|
||||
} else {
|
||||
quantity = fmt.Sprintf("%d", asset.Quantity)
|
||||
}
|
||||
|
||||
a.assetTable.SetCell(row, 2,
|
||||
tview.NewTableCell(quantity).
|
||||
SetExpansion(a.assetTableExpansions[2]).
|
||||
SetAlign(tview.AlignLeft))
|
||||
|
||||
shelfLocation := ""
|
||||
if asset.ShelfLocation != nil {
|
||||
shelfLocation = asset.ShelfLocation.Name
|
||||
}
|
||||
|
||||
a.assetTable.SetCell(row, 3,
|
||||
tview.NewTableCell(shelfLocation).
|
||||
SetExpansion(a.assetTableExpansions[3]).
|
||||
SetAlign(tview.AlignLeft))
|
||||
|
||||
a.assetTable.SetCell(row, 4,
|
||||
tview.NewTableCell(asset.Manufacturer).
|
||||
SetExpansion(a.assetTableExpansions[4]).
|
||||
SetAlign(tview.AlignLeft))
|
||||
|
||||
a.assetTable.SetCell(row, 5,
|
||||
tview.NewTableCell(asset.ModelName).
|
||||
SetExpansion(a.assetTableExpansions[5]).
|
||||
SetAlign(tview.AlignLeft))
|
||||
|
||||
category := ""
|
||||
if asset.Category != nil {
|
||||
category = asset.Category.Name
|
||||
}
|
||||
|
||||
a.assetTable.SetCell(row, 6,
|
||||
tview.NewTableCell(category).
|
||||
SetExpansion(a.assetTableExpansions[6]).
|
||||
SetAlign(tview.AlignLeft))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Assets) updateAssetTableTitle(count int) {
|
||||
dirtyFlag := ""
|
||||
if a.assetList.dirty {
|
||||
dirtyFlag = "*"
|
||||
}
|
||||
title := fmt.Sprintf("[::b]ASSETS [%s%d]", dirtyFlag, count)
|
||||
a.assetTable.SetTitle(title)
|
||||
}
|
||||
|
||||
func (a *Assets) writeTableHeaders() {
|
||||
for i, headerText := range a.assetTableHeaders {
|
||||
header := fmt.Sprintf("[::b]%s", strings.ToUpper(headerText))
|
||||
a.assetTable.SetCell(0, i,
|
||||
tview.NewTableCell(header).
|
||||
SetExpansion(a.assetTableExpansions[i]).
|
||||
SetBackgroundColor(style.TableHeaderBgColor).
|
||||
SetTextColor(style.TableHeaderFgColor).
|
||||
SetAlign(tview.AlignLeft).
|
||||
SetSelectable(false))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user