Blank root panel, asset form as a modal, and a dedicated Statuses page

- Main panel is now blank at the top level ("Select a folder or asset
  from the tree") instead of showing a dashboard - browsing happens in
  the tree, the panel only shows something once you click into it.
  Removed the now-dead DashboardStats/Stats() machinery it depended on.
- Add/Edit Asset no longer takes over the main panel; it opens in
  #assetModal instead; asset_form.html was trimmed down to just the form
  (no more breadcrumb/panel wrapper) since the modal chrome provides the
  title and footer buttons now. The dynamic item-type-fields and submit
  listeners moved from #main-panel's delegation to the modal element's.
- Statuses split out of the General settings page into its own
  /settings/statuses page and sub-nav entry, alongside General, Item
  Types, and Users.
This commit is contained in:
Owen
2026-08-17 15:45:38 -05:00
parent 4d7ebed68e
commit 8d9106542c
12 changed files with 254 additions and 436 deletions
-81
View File
@@ -466,84 +466,3 @@ func (s *Store) descendantFolderIDs(ctx context.Context, rootID int64) ([]int64,
walk(rootID)
return out, nil
}
// DashboardStats summarizes inventory counts for the small dashboard.
type DashboardStats struct {
Total int
ByStatus []StatusCount
ByType []TypeCount
RecentlyAdded []models.Asset
}
type StatusCount struct {
Name string
Color string
Count int
}
type TypeCount struct {
Name string
Icon string
Count int
}
func (s *Store) Stats(ctx context.Context) (*DashboardStats, error) {
stats := &DashboardStats{}
if err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM assets`).Scan(&stats.Total); err != nil {
return nil, err
}
rows, err := s.db.QueryContext(ctx, `
SELECT st.name, st.color, COUNT(a.id)
FROM statuses st LEFT JOIN assets a ON a.status_id = st.id
GROUP BY st.id ORDER BY st.sort_order ASC`)
if err != nil {
return nil, err
}
for rows.Next() {
var sc StatusCount
if err := rows.Scan(&sc.Name, &sc.Color, &sc.Count); err != nil {
rows.Close()
return nil, err
}
stats.ByStatus = append(stats.ByStatus, sc)
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
rows, err = s.db.QueryContext(ctx, `
SELECT it.name, it.icon, COUNT(a.id)
FROM item_types it LEFT JOIN assets a ON a.item_type_id = it.id
GROUP BY it.id HAVING COUNT(a.id) > 0 ORDER BY COUNT(a.id) DESC`)
if err != nil {
return nil, err
}
for rows.Next() {
var tc TypeCount
if err := rows.Scan(&tc.Name, &tc.Icon, &tc.Count); err != nil {
rows.Close()
return nil, err
}
stats.ByType = append(stats.ByType, tc)
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
recentRows, err := s.db.QueryContext(ctx, assetSelect+` ORDER BY a.created_at DESC LIMIT 8`)
if err != nil {
return nil, err
}
defer recentRows.Close()
for recentRows.Next() {
a, err := scanAsset(recentRows)
if err != nil {
return nil, err
}
stats.RecentlyAdded = append(stats.RecentlyAdded, *a)
}
return stats, recentRows.Err()
}