From 1d99a8f659ebd807a8aab69bd9739a6f5b142a3a Mon Sep 17 00:00:00 2001 From: root Date: Tue, 3 Feb 2026 09:17:07 -0500 Subject: [PATCH] feat: add classification-based filtering and fix company name display - Added classification column to companies table - Created excluded_classifications setting (default: Tools Only) - Updated kiosk stats and activity APIs to filter by classification - Added comprehensive classification filtering UI with checkboxes - Support for common classifications (Tools Only, Wulff Consulting Client, Customer) - Allow custom classification entry - Fixed company name display in settings (convert string IDs properly) - Classification filtering works alongside company ID exclusions --- app/api/kiosk/activity/route.ts | 35 +++++- app/api/kiosk/stats/route.ts | 33 +++++- app/kiosk/settings/page.tsx | 104 +++++++++++++++++- migrations/020_add_company_classification.sql | 14 +++ 4 files changed, 176 insertions(+), 10 deletions(-) create mode 100644 migrations/020_add_company_classification.sql diff --git a/app/api/kiosk/activity/route.ts b/app/api/kiosk/activity/route.ts index 29f51e5..579dede 100644 --- a/app/api/kiosk/activity/route.ts +++ b/app/api/kiosk/activity/route.ts @@ -14,15 +14,40 @@ async function getExcludedCompanyIds(): Promise { } } +async function getExcludedClassifications(): Promise { + try { + const result = await postgresClient.query( + `SELECT setting_value FROM kiosk_settings WHERE setting_key = 'excluded_classifications'` + ); + const value = result.rows[0]?.setting_value || ''; + return value ? value.split(',').map((c: string) => c.trim()).filter(Boolean) : []; + } catch (error) { + console.error('Error fetching excluded classifications:', error); + return []; + } +} + export async function GET(request: NextRequest) { try { - // Get excluded company IDs (co-managed clients) + // Get excluded company IDs (co-managed clients) and classifications const excludedCompanyIds = await getExcludedCompanyIds(); - const excludeCompanyFilter = excludedCompanyIds.length > 0 - ? `AND t.company_id NOT IN (${excludedCompanyIds.join(',')})` - : ''; + const excludedClassifications = await getExcludedClassifications(); + + // Build company exclusion filter + let excludeCompanyFilter = ''; + if (excludedCompanyIds.length > 0 || excludedClassifications.length > 0) { + const conditions = []; + if (excludedCompanyIds.length > 0) { + conditions.push(`t.company_id NOT IN (${excludedCompanyIds.join(',')})`); + } + if (excludedClassifications.length > 0) { + const classificationList = excludedClassifications.map(c => `'${c.replace(/'/g, "''")}'`).join(','); + conditions.push(`t.company_id NOT IN (SELECT id FROM companies WHERE classification IN (${classificationList}))`); + } + excludeCompanyFilter = `AND (${conditions.join(' AND ')})`; + } - // Get recent ticket activity for ticker feed - exclude RMM alerts (source = 8) and co-managed clients + // Get recent ticket activity for ticker feed - exclude RMM alerts (source = 8), co-managed clients, and excluded classifications const activityResult = await postgresClient.query( `SELECT t.ticket_number, diff --git a/app/api/kiosk/stats/route.ts b/app/api/kiosk/stats/route.ts index 89b6d2a..de2e13a 100644 --- a/app/api/kiosk/stats/route.ts +++ b/app/api/kiosk/stats/route.ts @@ -14,15 +14,40 @@ async function getExcludedCompanyIds(): Promise { } } +async function getExcludedClassifications(): Promise { + try { + const result = await postgresClient.query( + `SELECT setting_value FROM kiosk_settings WHERE setting_key = 'excluded_classifications'` + ); + const value = result.rows[0]?.setting_value || ''; + return value ? value.split(',').map((c: string) => c.trim()).filter(Boolean) : []; + } catch (error) { + console.error('Error fetching excluded classifications:', error); + return []; + } +} + export async function GET(request: NextRequest) { try { // Get excluded company IDs (co-managed clients) const excludedCompanyIds = await getExcludedCompanyIds(); - const excludeCompanyFilter = excludedCompanyIds.length > 0 - ? `AND company_id NOT IN (${excludedCompanyIds.join(',')})` - : ''; + const excludedClassifications = await getExcludedClassifications(); + + // Build company exclusion filter + let excludeCompanyFilter = ''; + if (excludedCompanyIds.length > 0 || excludedClassifications.length > 0) { + const conditions = []; + if (excludedCompanyIds.length > 0) { + conditions.push(`company_id NOT IN (${excludedCompanyIds.join(',')})`); + } + if (excludedClassifications.length > 0) { + const classificationList = excludedClassifications.map(c => `'${c.replace(/'/g, "''")}'`).join(','); + conditions.push(`company_id NOT IN (SELECT id FROM companies WHERE classification IN (${classificationList}))`); + } + excludeCompanyFilter = `AND (${conditions.join(' AND ')})`; + } - // Critical tickets (Priority 1-3) - exclude RMM alerts (source = 8) and co-managed clients + // Critical tickets (Priority 1-3) - exclude RMM alerts (source = 8), co-managed clients, and excluded classifications const criticalTicketsResult = await postgresClient.query( `SELECT COUNT(*) as count FROM tickets diff --git a/app/kiosk/settings/page.tsx b/app/kiosk/settings/page.tsx index 3f43492..ddc84e6 100644 --- a/app/kiosk/settings/page.tsx +++ b/app/kiosk/settings/page.tsx @@ -12,6 +12,7 @@ interface Company { interface KioskSettings { excluded_company_ids: number[]; + excluded_classifications: string[]; cycle_interval: number; refresh_interval: number; show_rmm_alerts: boolean; @@ -21,6 +22,7 @@ export default function KioskSettingsPage() { const router = useRouter(); const [settings, setSettings] = useState({ excluded_company_ids: [], + excluded_classifications: [], cycle_interval: 7, refresh_interval: 60, show_rmm_alerts: false, @@ -99,6 +101,14 @@ export default function KioskSettingsPage() { setting_value: settings.excluded_company_ids, }), }), + fetch('/api/kiosk/settings', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + setting_key: 'excluded_classifications', + setting_value: settings.excluded_classifications, + }), + }), fetch('/api/kiosk/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -135,7 +145,9 @@ export default function KioskSettingsPage() { }; const getCompanyName = (companyId: number) => { - return companies.find(c => c.id === companyId)?.companyName || `Company ${companyId}`; + // API returns id as string, settings stores as number - convert for comparison + const idStr = companyId.toString(); + return companies.find(c => c.id.toString() === idStr)?.companyName || `Company ${companyId}`; }; if (loading) { @@ -232,6 +244,96 @@ export default function KioskSettingsPage() { + {/* Excluded Classifications Section */} +
+

Excluded Company Classifications

+

+ Tickets from companies with these classifications will not appear in the kiosk display. +

+ + {/* Common Classifications */} +
+ {['Tools Only', 'Wulff Consulting Client', 'Customer'].map(classification => ( + + ))} +
+ + {/* Custom Classification Input */} +
+ +
+ { + if (e.key === 'Enter') { + const value = (e.target as HTMLInputElement).value.trim(); + if (value && !settings.excluded_classifications.includes(value)) { + setSettings({ + ...settings, + excluded_classifications: [...settings.excluded_classifications, value], + }); + (e.target as HTMLInputElement).value = ''; + } + } + }} + /> +
+

Press Enter to add

+
+ + {/* Custom Classifications List */} + {settings.excluded_classifications.filter(c => !['Tools Only', 'Wulff Consulting Client', 'Customer'].includes(c)).length > 0 && ( +
+
Custom classifications:
+ {settings.excluded_classifications + .filter(c => !['Tools Only', 'Wulff Consulting Client', 'Customer'].includes(c)) + .map(classification => ( +
+ {classification} + +
+ ))} +
+ )} +
+ {/* Display Settings */}

Display Settings

diff --git a/migrations/020_add_company_classification.sql b/migrations/020_add_company_classification.sql new file mode 100644 index 0000000..577281e --- /dev/null +++ b/migrations/020_add_company_classification.sql @@ -0,0 +1,14 @@ +-- Add classification column to companies table +-- This will store the classification name (e.g., "Tools Only", "Wulff Consulting Client", "Customer") +ALTER TABLE companies ADD COLUMN IF NOT EXISTS classification VARCHAR(255); + +-- Create index for faster filtering +CREATE INDEX IF NOT EXISTS idx_companies_classification ON companies(classification); + +-- Add classification to kiosk settings +INSERT INTO kiosk_settings (setting_key, setting_value, description) VALUES + ('excluded_classifications', 'Tools Only', 'Comma-separated list of company classifications to exclude from kiosk') +ON CONFLICT (setting_key) DO NOTHING; + +-- Add comment +COMMENT ON COLUMN companies.classification IS 'Company classification (e.g., Tools Only, Wulff Consulting Client, Customer)';