feat: classification labels in data browser + kiosk recurring revenue filter

- data-browser/companies: resolve classification picklist IDs to labels in
  table column and detail modal; also added to DetailModal COMPANY_GROUPS
- DetailModal: add 'classification' FieldType with color-coded badge map
- kiosk stats + activity: switch from label-based exclusion to ID-based
  allowlist (included_classifications). Only shows companies with
  classification IN (15,16,17,18,203,205,206,207,202,5,12)
  = managed service / recurring revenue tiers only
This commit is contained in:
lorentz 2026-03-30 14:56:02 -04:00
parent 5f0fbb4734
commit a98c0daf15
4 changed files with 84 additions and 34 deletions

View file

@ -9,6 +9,32 @@ import { Badge } from '@/components/ui/badge';
import { ArrowLeft, Users } from 'lucide-react';
import Link from 'next/link';
const CLASSIFICATION_LABELS: Record<number, string> = {
5: 'Block Hour',
9: 'Canceled',
202: 'Co-Managed',
16: 'Gold (Legacy)',
206: 'IT Complete w/ Gold Security',
207: 'IT Core / Silver Security',
203: 'IT Foundation / Bronze Security',
205: 'IT Premier / Platinum Security',
14: 'Jeopardy Company',
201: 'Partner',
15: 'Platinum (Legacy)',
13: 'Residential (no-pay)',
17: 'Silver (Legacy)',
12: 'T&M',
7: 'Target',
200: 'Tools Only',
18: 'Bronze (Legacy)',
};
function classificationLabel(val: any): string {
if (val === null || val === undefined || val === '') return '—';
const num = parseInt(String(val), 10);
return CLASSIFICATION_LABELS[num] ?? String(val);
}
export default function CompaniesBrowserPage() {
const [companies, setCompanies] = useState([]);
const [totalCount, setTotalCount] = useState(0);
@ -87,6 +113,7 @@ export default function CompaniesBrowserPage() {
key: 'classification',
label: 'Classification',
sortable: true,
render: (value: any) => classificationLabel(value),
},
{
key: 'is_active',
@ -124,7 +151,7 @@ export default function CompaniesBrowserPage() {
{ key: 'state', label: 'State' },
{ key: 'postal_code', label: 'Postal Code' },
{ key: 'country', label: 'Country' },
{ key: 'classification', label: 'Classification' },
{ key: 'classification', label: 'Classification', render: (v: any) => classificationLabel(v) },
{ key: 'company_type', label: 'Company Type' },
{ key: 'company_category_id', label: 'Company Category ID' },
{ key: 'owner_resource_id', label: 'Owner Resource ID' },

View file

@ -14,15 +14,15 @@ async function getExcludedCompanyIds(): Promise<number[]> {
}
}
async function getExcludedClassifications(): Promise<string[]> {
async function getIncludedClassificationIds(): Promise<number[]> {
try {
const result = await postgresClient.query(
`SELECT setting_value FROM kiosk_settings WHERE setting_key = 'excluded_classifications'`
`SELECT setting_value FROM kiosk_settings WHERE setting_key = 'included_classifications'`
);
const value = result.rows[0]?.setting_value || '';
return value ? value.split(',').map((c: string) => c.trim()).filter(Boolean) : [];
return value ? value.split(',').map((c: string) => parseInt(c.trim(), 10)).filter((n: number) => !isNaN(n)) : [];
} catch (error) {
console.error('Error fetching excluded classifications:', error);
console.error('Error fetching included classifications:', error);
return [];
}
}
@ -31,19 +31,18 @@ export async function GET(request: NextRequest) {
try {
// Get excluded company IDs (co-managed clients) and classifications
const excludedCompanyIds = await getExcludedCompanyIds();
const excludedClassifications = await getExcludedClassifications();
// Build company exclusion filter
const includedClassificationIds = await getIncludedClassificationIds();
// Build company filter: only show recurring revenue classification clients
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}))`);
}
const conditions: string[] = [];
if (excludedCompanyIds.length > 0) {
conditions.push(`t.company_id NOT IN (${excludedCompanyIds.join(',')})`);
}
if (includedClassificationIds.length > 0) {
conditions.push(`t.company_id IN (SELECT id FROM companies WHERE classification::integer IN (${includedClassificationIds.join(',')}))`);
}
if (conditions.length > 0) {
excludeCompanyFilter = `AND (${conditions.join(' AND ')})`;
}

View file

@ -14,15 +14,15 @@ async function getExcludedCompanyIds(): Promise<number[]> {
}
}
async function getExcludedClassifications(): Promise<string[]> {
async function getIncludedClassificationIds(): Promise<number[]> {
try {
const result = await postgresClient.query(
`SELECT setting_value FROM kiosk_settings WHERE setting_key = 'excluded_classifications'`
`SELECT setting_value FROM kiosk_settings WHERE setting_key = 'included_classifications'`
);
const value = result.rows[0]?.setting_value || '';
return value ? value.split(',').map((c: string) => c.trim()).filter(Boolean) : [];
return value ? value.split(',').map((c: string) => parseInt(c.trim(), 10)).filter((n: number) => !isNaN(n)) : [];
} catch (error) {
console.error('Error fetching excluded classifications:', error);
console.error('Error fetching included classifications:', error);
return [];
}
}
@ -31,19 +31,18 @@ export async function GET(request: NextRequest) {
try {
// Get excluded company IDs (co-managed clients)
const excludedCompanyIds = await getExcludedCompanyIds();
const excludedClassifications = await getExcludedClassifications();
// Build company exclusion filter
const includedClassificationIds = await getIncludedClassificationIds();
// Build company filter: only show recurring revenue classification clients
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}))`);
}
const conditions: string[] = [];
if (excludedCompanyIds.length > 0) {
conditions.push(`company_id NOT IN (${excludedCompanyIds.join(',')})`);
}
if (includedClassificationIds.length > 0) {
conditions.push(`company_id IN (SELECT id FROM companies WHERE classification::integer IN (${includedClassificationIds.join(',')}))`);
}
if (conditions.length > 0) {
excludeCompanyFilter = `AND (${conditions.join(' AND ')})`;
}