From 7fd1ed83810de503c20dfdf1e4845c6c2405d7d2 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Wed, 1 Jul 2026 18:26:01 +0000 Subject: [PATCH] Add app category grouping support Add category column to apps table with migration, update seed with category values for existing apps, and include category in auth queries ordered by category then name. Co-Authored-By: Claude Sonnet 4.6 --- src/db.js | 22 +++++++++++++--------- src/routes/auth.js | 8 ++++---- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/db.js b/src/db.js index 8429269..ba3280e 100644 --- a/src/db.js +++ b/src/db.js @@ -23,8 +23,9 @@ export async function initDb() { name TEXT NOT NULL, description TEXT, base_path TEXT NOT NULL, - icon TEXT NOT NULL DEFAULT '📋', + icon TEXT NOT NULL DEFAULT 'ClipboardList', theme_color TEXT NOT NULL DEFAULT '#1e3a5f', + category VARCHAR(100), active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); @@ -37,17 +38,20 @@ export async function initDb() { ); `) + // Add category column to existing installs + await pool.query(`ALTER TABLE apps ADD COLUMN IF NOT EXISTS category VARCHAR(100)`) + // Seed built-in apps await pool.query(` - INSERT INTO apps (slug, name, description, base_path, icon, theme_color) + INSERT INTO apps (slug, name, description, base_path, icon, theme_color, category) VALUES - ('noticeboard', 'Noticeboard', 'Staff notices and announcements', '/notices', 'ClipboardList', '#1e3a5f'), - ('kitchen', 'Kitchen Flash','Invoice processing and GP tracking', '/kitchen', 'ChefHat', '#e85d04'), - ('cashup', 'Cash Up', 'Hotel daily cashing up', '/cashup', 'Banknote', '#6b2d8b'), - ('housekeeping','Housekeeping', 'Room status and task management', '/hk', 'BedDouble', '#2d6a4f'), - ('forecasting', 'Forecasting', 'Revenue forecasting and reporting', '/forecast','TrendingUp', '#0077b6'), - ('rates', 'Rate Scraper', 'Competitor rate monitoring', '/rates', 'Tag', '#7b4f00') - ON CONFLICT (slug) DO UPDATE SET icon = EXCLUDED.icon + ('noticeboard', 'Noticeboard', 'Staff notices and announcements', '/notices', 'ClipboardList', '#1e3a5f', NULL), + ('kitchen', 'Kitchen Flash','Invoice processing and GP tracking', '/kitchen', 'ChefHat', '#e85d04', 'Kitchen'), + ('cashup', 'Cash Up', 'Hotel daily cashing up', '/cashup', 'Banknote', '#6b2d8b', 'Finance'), + ('housekeeping','Housekeeping', 'Room status and task management', '/hk', 'BedDouble', '#2d6a4f', 'Housekeeping'), + ('forecasting', 'Forecasting', 'Revenue forecasting and reporting', '/forecast','TrendingUp', '#0077b6', 'Finance'), + ('rates', 'Rate Scraper', 'Competitor rate monitoring', '/rates', 'Tag', '#7b4f00', 'Finance') + ON CONFLICT (slug) DO UPDATE SET icon = EXCLUDED.icon, category = EXCLUDED.category `) // Seed first admin user if table is empty diff --git a/src/routes/auth.js b/src/routes/auth.js index 50d96cd..6344f15 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -32,17 +32,17 @@ async function getUserWithApps(userId) { // only the apps explicitly granted to them via user_app_perms. const { rows: apps } = user.is_admin ? await pool.query( - `SELECT a.slug, a.name, a.description, a.base_path, a.icon, a.theme_color + `SELECT a.slug, a.name, a.description, a.base_path, a.icon, a.theme_color, a.category FROM apps a WHERE a.active = true - ORDER BY a.name` + ORDER BY a.category NULLS FIRST, a.name` ) : await pool.query( - `SELECT a.slug, a.name, a.description, a.base_path, a.icon, a.theme_color + `SELECT a.slug, a.name, a.description, a.base_path, a.icon, a.theme_color, a.category FROM apps a JOIN user_app_perms p ON p.app_id = a.id WHERE p.user_id = $1 AND a.active = true - ORDER BY a.name`, + ORDER BY a.category NULLS FIRST, a.name`, [userId] )