initial commit

This commit is contained in:
2024-01-18 00:03:13 -06:00
commit 8654ded7a0
34 changed files with 2650 additions and 0 deletions

View 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
}

View 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()
}

View 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
}

View 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
View 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)
})
}

View 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))
}
}