2025-11-19 14:18:16 -05:00
|
|
|
'use client';
|
|
|
|
|
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
|
2025-11-19 14:18:16 -05:00
|
|
|
import { Badge } from '@/components/ui/badge';
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
|
|
|
|
import { Separator } from '@/components/ui/separator';
|
2026-02-20 10:28:15 -05:00
|
|
|
import { Calendar, Check, X, Copy, CheckCircle2, Code2, LayoutTemplate, ExternalLink, Phone, Globe, Loader2, User, Building2, MessageSquare, Clock } from 'lucide-react';
|
2025-11-19 14:18:16 -05:00
|
|
|
import { Button } from '@/components/ui/button';
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
import { StatusBadge } from '@/components/ui/status-badge';
|
|
|
|
|
import {
|
|
|
|
|
priorityBadge,
|
|
|
|
|
ticketStatusBadge,
|
|
|
|
|
sourceBadge,
|
|
|
|
|
classificationBadge,
|
|
|
|
|
companyTypeBadge,
|
|
|
|
|
publishBadge,
|
|
|
|
|
activeBadge,
|
|
|
|
|
yesNoBadge,
|
|
|
|
|
billableBadge,
|
|
|
|
|
approvedBadge,
|
|
|
|
|
toneClass,
|
|
|
|
|
paletteClass,
|
|
|
|
|
} from '@/lib/status-registry';
|
2026-02-20 10:28:15 -05:00
|
|
|
import { useState, useEffect } from 'react';
|
2025-11-19 14:18:16 -05:00
|
|
|
|
2026-02-20 10:28:15 -05:00
|
|
|
// ── Live lookup types (fetched from DB) ───────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
interface Lookups {
|
|
|
|
|
statuses: Record<number, string>;
|
|
|
|
|
resources: Record<number, string>;
|
|
|
|
|
companies: Record<number, string>;
|
|
|
|
|
issueTypes: Record<number, string>;
|
|
|
|
|
subIssueTypes: Record<number, string>;
|
|
|
|
|
queues: Record<number, string>;
|
|
|
|
|
configItems: Record<number, string>;
|
|
|
|
|
}
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
|
|
|
|
|
// ── Field metadata for formatted view ─────────────────────────────────────────
|
|
|
|
|
|
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
2026-03-30 14:56:02 -04:00
|
|
|
type FieldType = 'date' | 'bool' | 'status' | 'priority' | 'source' | 'queue' | 'company_type' | 'classification' | 'url' | 'phone' | 'hours' | 'id' | 'resource' | 'company' | 'issue_type' | 'sub_issue_type' | 'config_item';
|
2026-02-20 10:28:15 -05:00
|
|
|
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
type FieldGroup = {
|
|
|
|
|
label: string;
|
2026-02-20 10:28:15 -05:00
|
|
|
fields: Array<{ key: string; label: string; type?: FieldType }>;
|
|
|
|
|
paired?: string;
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const TICKET_GROUPS: FieldGroup[] = [
|
|
|
|
|
{
|
|
|
|
|
label: 'Parties',
|
|
|
|
|
fields: [
|
2026-02-20 10:28:15 -05:00
|
|
|
{ key: 'company_id', label: 'Company', type: 'company' },
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
{ key: 'contact_id', label: 'Contact ID', type: 'id' },
|
2026-02-20 10:28:15 -05:00
|
|
|
{ key: 'assigned_resource_id', label: 'Assigned Resource', type: 'resource' },
|
|
|
|
|
{ key: 'configuration_item_id', label: 'Configuration Item', type: 'config_item' },
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-02-20 10:28:15 -05:00
|
|
|
label: 'Details',
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
fields: [
|
2026-02-20 10:28:15 -05:00
|
|
|
{ key: 'priority', label: 'Priority', type: 'priority' },
|
|
|
|
|
{ key: 'source', label: 'Source', type: 'source' },
|
|
|
|
|
{ key: 'queue_id', label: 'Queue', type: 'queue' },
|
|
|
|
|
{ key: 'issue_type', label: 'Issue Type', type: 'issue_type' },
|
|
|
|
|
{ key: 'sub_issue_type', label: 'Sub-Issue Type', type: 'sub_issue_type' },
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Dates & Time',
|
2026-02-20 10:28:15 -05:00
|
|
|
paired: 'System',
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
fields: [
|
|
|
|
|
{ key: 'create_date', label: 'Created', type: 'date' },
|
|
|
|
|
{ key: 'due_date_time', label: 'Due', type: 'date' },
|
|
|
|
|
{ key: 'last_activity_date', label: 'Last Activity', type: 'date' },
|
|
|
|
|
{ key: 'completed_date', label: 'Completed', type: 'date' },
|
|
|
|
|
{ key: 'estimated_hours', label: 'Estimated Hours', type: 'hours' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'System',
|
2026-02-20 10:28:15 -05:00
|
|
|
paired: 'Dates & Time',
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
fields: [
|
|
|
|
|
{ key: 'id', label: 'Record ID', type: 'id' },
|
|
|
|
|
{ key: 'synced_at', label: 'Synced At', type: 'date' },
|
|
|
|
|
{ key: 'is_deleted', label: 'Deleted', type: 'bool' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const COMPANY_GROUPS: FieldGroup[] = [
|
|
|
|
|
{
|
|
|
|
|
label: 'Identity',
|
|
|
|
|
fields: [
|
|
|
|
|
{ key: 'company_name', label: 'Company Name' },
|
|
|
|
|
{ key: 'company_number', label: 'Company #' },
|
|
|
|
|
{ key: 'company_type', label: 'Type', type: 'company_type' },
|
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
2026-03-30 14:56:02 -04:00
|
|
|
{ key: 'classification', label: 'Classification', type: 'classification' },
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
{ key: 'is_active', label: 'Active', type: 'bool' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Contact',
|
|
|
|
|
fields: [
|
|
|
|
|
{ key: 'phone', label: 'Phone', type: 'phone' },
|
|
|
|
|
{ key: 'alternate_phone1', label: 'Alt Phone 1', type: 'phone' },
|
|
|
|
|
{ key: 'alternate_phone2', label: 'Alt Phone 2', type: 'phone' },
|
|
|
|
|
{ key: 'fax', label: 'Fax', type: 'phone' },
|
|
|
|
|
{ key: 'web_site_url', label: 'Website', type: 'url' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'Address',
|
|
|
|
|
fields: [
|
|
|
|
|
{ key: 'address1', label: 'Address 1' },
|
|
|
|
|
{ key: 'address2', label: 'Address 2' },
|
|
|
|
|
{ key: 'city', label: 'City' },
|
|
|
|
|
{ key: 'state', label: 'State' },
|
|
|
|
|
{ key: 'postal_code', label: 'Postal Code' },
|
|
|
|
|
{ key: 'country', label: 'Country' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: 'System',
|
2026-02-20 10:28:15 -05:00
|
|
|
paired: 'Address',
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
fields: [
|
|
|
|
|
{ key: 'id', label: 'Record ID', type: 'id' },
|
|
|
|
|
{ key: 'synced_at', label: 'Synced At', type: 'date' },
|
|
|
|
|
{ key: 'is_deleted', label: 'Deleted', type: 'bool' },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-02-20 10:28:15 -05:00
|
|
|
function resolveLabel(key: string, value: any, type: FieldType | undefined, lookups: Lookups): { display: React.ReactNode; isEmpty: boolean } {
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
if (value === null || value === undefined || value === '') {
|
2026-02-20 10:28:15 -05:00
|
|
|
return { display: <span className="text-muted-foreground/40 italic text-xs">—</span>, isEmpty: true };
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
case 'bool': {
|
|
|
|
|
const badge = yesNoBadge(Boolean(value));
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
return {
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
display: (
|
|
|
|
|
<StatusBadge variantClass={badge.variantClass}>
|
|
|
|
|
{value ? <Check className="w-3 h-3 mr-1" /> : <X className="w-3 h-3 mr-1" />}
|
|
|
|
|
{badge.label}
|
|
|
|
|
</StatusBadge>
|
|
|
|
|
),
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
isEmpty: false,
|
|
|
|
|
};
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
}
|
2026-02-20 10:28:15 -05:00
|
|
|
case 'date': {
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
try {
|
|
|
|
|
const d = new Date(value);
|
|
|
|
|
return {
|
|
|
|
|
display: (
|
|
|
|
|
<span className="inline-flex items-center gap-1.5 text-sm">
|
|
|
|
|
<Calendar className="w-3.5 h-3.5 text-muted-foreground" />
|
|
|
|
|
{d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })}
|
|
|
|
|
</span>
|
|
|
|
|
),
|
|
|
|
|
isEmpty: false,
|
|
|
|
|
};
|
|
|
|
|
} catch { break; }
|
2026-02-20 10:28:15 -05:00
|
|
|
}
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
case 'status': {
|
2026-02-20 10:28:15 -05:00
|
|
|
const label = lookups.statuses[Number(value)] ?? `Status ${value}`;
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
const badge = ticketStatusBadge(label);
|
|
|
|
|
return { display: <StatusBadge {...badge} />, isEmpty: false };
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
}
|
|
|
|
|
case 'priority': {
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
return { display: <StatusBadge {...priorityBadge(Number(value))} />, isEmpty: false };
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
}
|
|
|
|
|
case 'source': {
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
return { display: <StatusBadge {...sourceBadge(Number(value))} />, isEmpty: false };
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
}
|
|
|
|
|
case 'queue': {
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
const label = lookups.queues[Number(value)] ?? `Queue ${value}`;
|
|
|
|
|
return { display: <StatusBadge variantClass={paletteClass('indigo')}>{label}</StatusBadge>, isEmpty: false };
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
}
|
|
|
|
|
case 'company_type': {
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
return { display: <StatusBadge {...companyTypeBadge(Number(value))} />, isEmpty: false };
|
2026-02-20 10:28:15 -05:00
|
|
|
}
|
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
2026-03-30 14:56:02 -04:00
|
|
|
case 'classification': {
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
return { display: <StatusBadge {...classificationBadge(Number(value))} />, isEmpty: false };
|
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
2026-03-30 14:56:02 -04:00
|
|
|
}
|
2026-02-20 10:28:15 -05:00
|
|
|
case 'resource': {
|
|
|
|
|
const name = lookups.resources[Number(value)];
|
|
|
|
|
return {
|
|
|
|
|
display: name
|
|
|
|
|
? <span className="inline-flex items-center gap-1.5 text-sm"><User className="w-3.5 h-3.5 text-muted-foreground" />{name}</span>
|
|
|
|
|
: <span className="font-mono text-xs bg-muted px-2 py-0.5 rounded">{value}</span>,
|
|
|
|
|
isEmpty: false,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
case 'company': {
|
|
|
|
|
const name = lookups.companies[Number(value)];
|
|
|
|
|
return {
|
|
|
|
|
display: name
|
|
|
|
|
? <span className="inline-flex items-center gap-1.5 text-sm"><Building2 className="w-3.5 h-3.5 text-muted-foreground" />{name}</span>
|
|
|
|
|
: <span className="font-mono text-xs bg-muted px-2 py-0.5 rounded">{value}</span>,
|
|
|
|
|
isEmpty: false,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
case 'issue_type': {
|
|
|
|
|
const label = lookups.issueTypes[Number(value)] ?? `Issue ${value}`;
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
return { display: <StatusBadge variantClass={paletteClass('sky')}>{label}</StatusBadge>, isEmpty: false };
|
2026-02-20 10:28:15 -05:00
|
|
|
}
|
|
|
|
|
case 'sub_issue_type': {
|
|
|
|
|
const label = lookups.subIssueTypes[Number(value)] ?? `Sub-Issue ${value}`;
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
return { display: <StatusBadge variantClass="bg-sky-500/10 text-sky-700 dark:text-sky-400">{label}</StatusBadge>, isEmpty: false };
|
2026-02-20 10:28:15 -05:00
|
|
|
}
|
|
|
|
|
case 'config_item': {
|
|
|
|
|
const name = lookups.configItems[Number(value)];
|
|
|
|
|
return {
|
|
|
|
|
display: name
|
|
|
|
|
? <span className="text-sm">{name}</span>
|
|
|
|
|
: <span className="font-mono text-xs bg-muted px-2 py-0.5 rounded">{value}</span>,
|
|
|
|
|
isEmpty: false,
|
|
|
|
|
};
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
}
|
|
|
|
|
case 'url':
|
|
|
|
|
return {
|
|
|
|
|
display: (
|
|
|
|
|
<a href={String(value).startsWith('http') ? value : `https://${value}`} target="_blank" rel="noopener noreferrer"
|
|
|
|
|
className="inline-flex items-center gap-1 text-sm text-blue-500 hover:underline">
|
2026-02-20 10:28:15 -05:00
|
|
|
<Globe className="w-3.5 h-3.5" />{value}<ExternalLink className="w-3 h-3" />
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
</a>
|
|
|
|
|
),
|
|
|
|
|
isEmpty: false,
|
|
|
|
|
};
|
|
|
|
|
case 'phone':
|
|
|
|
|
return {
|
|
|
|
|
display: (
|
|
|
|
|
<a href={`tel:${value}`} className="inline-flex items-center gap-1 text-sm hover:underline">
|
|
|
|
|
<Phone className="w-3.5 h-3.5 text-muted-foreground" />{value}
|
|
|
|
|
</a>
|
|
|
|
|
),
|
|
|
|
|
isEmpty: false,
|
|
|
|
|
};
|
|
|
|
|
case 'id':
|
|
|
|
|
return { display: <span className="font-mono text-xs bg-muted px-2 py-0.5 rounded">{value}</span>, isEmpty: false };
|
|
|
|
|
case 'hours':
|
|
|
|
|
return { display: <span className="text-sm">{Number(value).toFixed(1)} hrs</span>, isEmpty: false };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}/)) {
|
2026-02-20 10:28:15 -05:00
|
|
|
return resolveLabel(key, value, 'date', lookups);
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { display: <span className="text-sm">{String(value)}</span>, isEmpty: false };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function detectGroups(data: Record<string, any>): FieldGroup[] {
|
|
|
|
|
if ('ticket_number' in data) return TICKET_GROUPS;
|
|
|
|
|
if ('company_name' in data) return COMPANY_GROUPS;
|
|
|
|
|
return [{ label: 'Fields', fields: Object.keys(data).map(k => ({ key: k, label: k })) }];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 10:28:15 -05:00
|
|
|
const EMPTY_LOOKUPS: Lookups = { statuses: {}, resources: {}, companies: {}, issueTypes: {}, subIssueTypes: {}, queues: {}, configItems: {} };
|
|
|
|
|
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
// ── Component ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
2025-11-19 14:18:16 -05:00
|
|
|
interface DetailModalProps {
|
|
|
|
|
open: boolean;
|
|
|
|
|
onOpenChange: (open: boolean) => void;
|
|
|
|
|
title: string;
|
|
|
|
|
data: Record<string, any> | null;
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
fields?: Array<{ key: string; label: string; render?: (value: any) => React.ReactNode }>;
|
2025-11-19 14:18:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function DetailModal({ open, onOpenChange, title, data, fields }: DetailModalProps) {
|
|
|
|
|
const [copiedField, setCopiedField] = useState<string | null>(null);
|
2026-02-20 10:28:15 -05:00
|
|
|
const [lookups, setLookups] = useState<Lookups>(EMPTY_LOOKUPS);
|
|
|
|
|
const [lookupsLoading, setLookupsLoading] = useState(false);
|
|
|
|
|
const [notes, setNotes] = useState<any[]>([]);
|
|
|
|
|
const [notesLoading, setNotesLoading] = useState(false);
|
|
|
|
|
const [timeEntries, setTimeEntries] = useState<any[]>([]);
|
|
|
|
|
const [timeEntriesLoading, setTimeEntriesLoading] = useState(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!open) return;
|
|
|
|
|
setLookupsLoading(true);
|
|
|
|
|
fetch('/api/data/lookups')
|
|
|
|
|
.then(r => r.json())
|
|
|
|
|
.then(d => {
|
|
|
|
|
setLookups({
|
|
|
|
|
statuses: Object.fromEntries((d.statuses ?? []).map((r: any) => [r.value, r.label])),
|
|
|
|
|
resources: Object.fromEntries((d.resources ?? []).map((r: any) => [r.id, r.name])),
|
|
|
|
|
companies: Object.fromEntries((d.companies ?? []).map((r: any) => [r.id, r.name])),
|
|
|
|
|
issueTypes: Object.fromEntries((d.issueTypes ?? []).map((r: any) => [r.value, r.label])),
|
|
|
|
|
subIssueTypes:Object.fromEntries((d.subIssueTypes?? []).map((r: any) => [r.value, r.label])),
|
|
|
|
|
queues: Object.fromEntries((d.queues ?? []).map((r: any) => [r.value, r.label])),
|
|
|
|
|
configItems: Object.fromEntries((d.configItems ?? []).map((r: any) => [r.id, r.name])),
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {})
|
|
|
|
|
.finally(() => setLookupsLoading(false));
|
|
|
|
|
|
|
|
|
|
if (data && 'ticket_number' in data && data.id) {
|
|
|
|
|
setNotesLoading(true);
|
|
|
|
|
fetch(`/api/data/ticket-notes?ticket_id=${data.id}&sort_by=create_date_time&sort_order=asc&limit=200`)
|
|
|
|
|
.then(r => r.json())
|
|
|
|
|
.then(d => setNotes(d.ticketNotes ?? []))
|
|
|
|
|
.catch(() => setNotes([]))
|
|
|
|
|
.finally(() => setNotesLoading(false));
|
|
|
|
|
|
|
|
|
|
setTimeEntriesLoading(true);
|
|
|
|
|
fetch(`/api/data/time-entries?ticket_id=${data.id}&sort_by=entry_date&sort_order=asc&limit=200`)
|
|
|
|
|
.then(r => r.json())
|
|
|
|
|
.then(d => setTimeEntries(d.timeEntries ?? []))
|
|
|
|
|
.catch(() => setTimeEntries([]))
|
|
|
|
|
.finally(() => setTimeEntriesLoading(false));
|
|
|
|
|
} else {
|
|
|
|
|
setNotes([]);
|
|
|
|
|
setTimeEntries([]);
|
|
|
|
|
}
|
|
|
|
|
}, [open]);
|
2025-11-19 14:18:16 -05:00
|
|
|
|
|
|
|
|
if (!data) return null;
|
|
|
|
|
|
|
|
|
|
const copyToClipboard = (text: string, fieldKey: string) => {
|
|
|
|
|
navigator.clipboard.writeText(text);
|
|
|
|
|
setCopiedField(fieldKey);
|
|
|
|
|
setTimeout(() => setCopiedField(null), 2000);
|
|
|
|
|
};
|
|
|
|
|
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
const groups = detectGroups(data);
|
|
|
|
|
|
|
|
|
|
// Raw tab: all fields
|
|
|
|
|
const rawFields = fields || Object.keys(data).map(k => ({ key: k, label: k, render: undefined }));
|
2025-11-19 14:18:16 -05:00
|
|
|
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
const renderRaw = (value: any): React.ReactNode => {
|
|
|
|
|
if (value === null || value === undefined) return <span className="text-muted-foreground/50 italic text-xs">null</span>;
|
|
|
|
|
if (typeof value === 'boolean') return <Badge variant={value ? 'default' : 'secondary'}>{value ? 'true' : 'false'}</Badge>;
|
|
|
|
|
if (typeof value === 'object') return <pre className="text-xs bg-muted p-2 rounded border overflow-x-auto">{JSON.stringify(value, null, 2)}</pre>;
|
|
|
|
|
return <span className="text-sm font-mono">{String(value)}</span>;
|
|
|
|
|
};
|
2025-11-19 14:18:16 -05:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
2026-02-20 10:28:15 -05:00
|
|
|
<DialogContent className="max-w-7xl max-h-[90vh] overflow-hidden flex flex-col gap-0 p-0 border-2 border-blue-500/70 shadow-[0_0_0_1px_rgba(59,130,246,0.15),0_20px_60px_-10px_rgba(59,130,246,0.25)]">
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
{/* Header */}
|
2026-02-20 10:28:15 -05:00
|
|
|
<div className="px-6 pt-5 pb-4 border-b">
|
|
|
|
|
{'ticket_number' in data ? (
|
|
|
|
|
<div className="flex items-start justify-between gap-6 pr-8">
|
|
|
|
|
<div className="min-w-0">
|
|
|
|
|
<div className="flex items-baseline gap-2 flex-wrap">
|
|
|
|
|
<span className="font-mono text-sm font-semibold text-muted-foreground shrink-0">{data.ticket_number}</span>
|
|
|
|
|
<DialogTitle className="text-xl font-bold leading-tight" style={{overflowWrap:'anywhere', wordBreak:'break-word'}}>{data.title}</DialogTitle>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="shrink-0 flex flex-col items-end gap-1">
|
|
|
|
|
{(() => {
|
|
|
|
|
const label = lookups.statuses[Number(data.status)] ?? `Status ${data.status}`;
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
return <StatusBadge {...ticketStatusBadge(label)} />;
|
2026-02-20 10:28:15 -05:00
|
|
|
})()}
|
|
|
|
|
</div>
|
2025-11-19 14:18:16 -05:00
|
|
|
</div>
|
2026-02-20 10:28:15 -05:00
|
|
|
) : (
|
|
|
|
|
<div className="flex items-start justify-between gap-4 pr-8">
|
|
|
|
|
<div>
|
|
|
|
|
<DialogTitle className="text-2xl font-bold">{title}</DialogTitle>
|
|
|
|
|
<DialogDescription className="mt-1">
|
|
|
|
|
Record ID: <span className="font-mono">{data.id}</span>
|
|
|
|
|
</DialogDescription>
|
|
|
|
|
</div>
|
|
|
|
|
{'is_active' in data && (
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
<StatusBadge {...activeBadge(Boolean(data.is_active))} />
|
2026-02-20 10:28:15 -05:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Tabs */}
|
|
|
|
|
<Tabs defaultValue="formatted" className="flex flex-col flex-1 overflow-hidden">
|
|
|
|
|
<div className="px-6 pt-3 pb-0 border-b">
|
|
|
|
|
<TabsList className="h-9">
|
|
|
|
|
<TabsTrigger value="formatted" className="gap-1.5">
|
|
|
|
|
<LayoutTemplate className="w-3.5 h-3.5" />
|
|
|
|
|
Formatted
|
|
|
|
|
</TabsTrigger>
|
2026-02-20 10:28:15 -05:00
|
|
|
{'ticket_number' in data && (
|
|
|
|
|
<TabsTrigger value="time" className="gap-1.5">
|
|
|
|
|
<Clock className="w-3.5 h-3.5" />
|
|
|
|
|
Time
|
|
|
|
|
{timeEntries.length > 0 && (
|
|
|
|
|
<span className="ml-1 rounded-full bg-blue-500/20 text-blue-600 text-xs px-1.5 py-0.5 font-medium">
|
|
|
|
|
{timeEntries.reduce((s, e) => s + (parseFloat(e.hours_worked) || 0), 0).toFixed(1)}h
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
)}
|
|
|
|
|
{'ticket_number' in data && (
|
|
|
|
|
<TabsTrigger value="notes" className="gap-1.5">
|
|
|
|
|
<MessageSquare className="w-3.5 h-3.5" />
|
|
|
|
|
Notes
|
|
|
|
|
{notes.length > 0 && (
|
|
|
|
|
<span className="ml-1 rounded-full bg-blue-500/20 text-blue-600 text-xs px-1.5 py-0.5 font-medium">{notes.length}</span>
|
|
|
|
|
)}
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
)}
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
<TabsTrigger value="raw" className="gap-1.5">
|
|
|
|
|
<Code2 className="w-3.5 h-3.5" />
|
|
|
|
|
Raw
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
</TabsList>
|
2025-11-19 14:18:16 -05:00
|
|
|
</div>
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
|
|
|
|
|
{/* ── Formatted Tab ── */}
|
|
|
|
|
<TabsContent value="formatted" className="flex-1 overflow-y-auto px-6 py-4 mt-0">
|
|
|
|
|
<div className="space-y-6">
|
2026-02-20 10:28:15 -05:00
|
|
|
{(() => {
|
|
|
|
|
const rendered = new Set<string>();
|
|
|
|
|
return groups.map((group) => {
|
|
|
|
|
if (rendered.has(group.label)) return null;
|
|
|
|
|
const visibleFields = group.fields.filter(f => f.key in data);
|
|
|
|
|
if (visibleFields.length === 0) return null;
|
|
|
|
|
|
|
|
|
|
// Inline badge row for Details group
|
|
|
|
|
if (group.label === 'Details') {
|
|
|
|
|
return (
|
|
|
|
|
<div key="Details">
|
|
|
|
|
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-3">Details</h3>
|
|
|
|
|
<div className="rounded-lg border overflow-hidden">
|
|
|
|
|
<div className="flex flex-wrap gap-x-6 gap-y-3 px-4 py-3">
|
|
|
|
|
{visibleFields.map((field) => {
|
|
|
|
|
const value = data[field.key];
|
|
|
|
|
const { display, isEmpty } = resolveLabel(field.key, value, field.type, lookups);
|
|
|
|
|
if (isEmpty) return null;
|
|
|
|
|
return (
|
|
|
|
|
<div key={field.key} className="flex items-center gap-1.5">
|
|
|
|
|
<span className="text-xs text-muted-foreground">{field.label}:</span>
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
{display}
|
|
|
|
|
</div>
|
2026-02-20 10:28:15 -05:00
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pairedGroup = group.paired ? groups.find(g => g.label === group.paired) : null;
|
|
|
|
|
const pairedVisible = pairedGroup ? pairedGroup.fields.filter(f => f.key in data) : [];
|
|
|
|
|
const isPaired = !!pairedGroup && pairedVisible.length > 0;
|
|
|
|
|
|
|
|
|
|
if (isPaired) {
|
|
|
|
|
rendered.add(group.label);
|
|
|
|
|
rendered.add(pairedGroup!.label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const renderGroupTable = (g: FieldGroup, fields: typeof visibleFields) => (
|
|
|
|
|
<div key={g.label} className="flex-1 min-w-0">
|
|
|
|
|
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-3">{g.label}</h3>
|
|
|
|
|
<div className="rounded-lg border overflow-hidden">
|
|
|
|
|
{fields.map((field, idx) => {
|
|
|
|
|
const value = data[field.key];
|
|
|
|
|
const { display, isEmpty } = resolveLabel(field.key, value, field.type, lookups);
|
|
|
|
|
const stringValue = value !== null && value !== undefined ? String(value) : '';
|
|
|
|
|
return (
|
|
|
|
|
<div key={field.key}>
|
|
|
|
|
{idx > 0 && <Separator />}
|
|
|
|
|
<div className="group grid grid-cols-[140px_1fr] items-start">
|
|
|
|
|
<div className="px-3 py-2.5 bg-muted/40 text-xs font-medium text-muted-foreground border-r truncate">
|
|
|
|
|
{field.label}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="px-3 py-2.5 flex items-start justify-between gap-2 min-w-0 overflow-hidden">
|
|
|
|
|
<div className={`flex-1 min-w-0 ${isEmpty ? 'opacity-40' : ''}`} style={{overflowWrap:'anywhere', wordBreak:'break-word'}}>
|
|
|
|
|
{display}
|
|
|
|
|
</div>
|
|
|
|
|
{stringValue && !isEmpty && (
|
|
|
|
|
<Button variant="ghost" size="icon"
|
|
|
|
|
className="h-5 w-5 opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
|
|
|
|
|
onClick={() => copyToClipboard(stringValue, `${g.label}-${field.key}`)}
|
|
|
|
|
>
|
|
|
|
|
{copiedField === `${g.label}-${field.key}`
|
|
|
|
|
? <CheckCircle2 className="w-3 h-3 text-green-500" />
|
|
|
|
|
: <Copy className="w-3 h-3" />}
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-20 10:28:15 -05:00
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
2025-11-19 14:18:16 -05:00
|
|
|
</div>
|
2026-02-20 10:28:15 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (isPaired) {
|
|
|
|
|
return (
|
|
|
|
|
<div key={group.label} className="grid grid-cols-2 gap-4">
|
|
|
|
|
{renderGroupTable(group, visibleFields)}
|
|
|
|
|
{renderGroupTable(pairedGroup!, pairedVisible)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div key={group.label}>
|
|
|
|
|
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-3">{group.label}</h3>
|
|
|
|
|
<div className="rounded-lg border overflow-hidden">
|
|
|
|
|
{visibleFields.map((field, idx) => {
|
|
|
|
|
const value = data[field.key];
|
|
|
|
|
const { display, isEmpty } = resolveLabel(field.key, value, field.type, lookups);
|
|
|
|
|
const stringValue = value !== null && value !== undefined ? String(value) : '';
|
|
|
|
|
return (
|
|
|
|
|
<div key={field.key}>
|
|
|
|
|
{idx > 0 && <Separator />}
|
|
|
|
|
<div className="group grid grid-cols-[220px_1fr] items-start">
|
|
|
|
|
<div className="px-4 py-3 bg-muted/40 text-sm font-medium text-muted-foreground border-r">
|
|
|
|
|
{field.label}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="px-4 py-3 flex items-start justify-between gap-2 min-w-0 overflow-hidden">
|
|
|
|
|
<div className={`flex-1 min-w-0 ${isEmpty ? 'opacity-40' : ''}`} style={{overflowWrap:'anywhere', wordBreak:'break-word'}}>
|
|
|
|
|
{display}
|
|
|
|
|
</div>
|
|
|
|
|
{stringValue && !isEmpty && (
|
|
|
|
|
<Button variant="ghost" size="icon"
|
|
|
|
|
className="h-6 w-6 opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
|
|
|
|
|
onClick={() => copyToClipboard(stringValue, field.key)}
|
|
|
|
|
>
|
|
|
|
|
{copiedField === field.key
|
|
|
|
|
? <CheckCircle2 className="w-3.5 h-3.5 text-green-500" />
|
|
|
|
|
: <Copy className="w-3.5 h-3.5" />}
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
})()}
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
|
|
|
|
|
{/* Description block for tickets */}
|
|
|
|
|
{'description' in data && data.description && (
|
|
|
|
|
<div>
|
|
|
|
|
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-3">Description</h3>
|
2026-02-20 10:28:15 -05:00
|
|
|
<div className="rounded-lg border p-4 text-sm whitespace-pre-wrap leading-relaxed text-muted-foreground break-words overflow-hidden" style={{overflowWrap:'anywhere', wordBreak:'break-word'}}>
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
{data.description}
|
2025-11-19 14:18:16 -05:00
|
|
|
</div>
|
|
|
|
|
</div>
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</TabsContent>
|
|
|
|
|
|
2026-02-20 10:28:15 -05:00
|
|
|
{/* ── Time Entries Tab ── */}
|
|
|
|
|
<TabsContent value="time" className="flex-1 overflow-y-auto px-6 py-4 mt-0">
|
|
|
|
|
{timeEntriesLoading ? (
|
|
|
|
|
<div className="flex items-center justify-center py-12">
|
|
|
|
|
<Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />
|
|
|
|
|
</div>
|
|
|
|
|
) : timeEntries.length === 0 ? (
|
|
|
|
|
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground gap-2">
|
|
|
|
|
<Clock className="w-8 h-8 opacity-30" />
|
|
|
|
|
<p className="text-sm">No time entries on this ticket</p>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
{/* Summary bar */}
|
|
|
|
|
<div className="rounded-lg border px-4 py-3 flex items-center gap-6 bg-muted/30 mb-4">
|
|
|
|
|
<div className="flex items-center gap-1.5 text-sm">
|
|
|
|
|
<Clock className="w-4 h-4 text-muted-foreground" />
|
|
|
|
|
<span className="font-semibold">{timeEntries.reduce((s, e) => s + (parseFloat(e.hours_worked) || 0), 0).toFixed(2)}</span>
|
|
|
|
|
<span className="text-muted-foreground">total hours</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-sm text-muted-foreground">{timeEntries.length} {timeEntries.length === 1 ? 'entry' : 'entries'}</div>
|
|
|
|
|
<div className="text-sm text-muted-foreground">
|
|
|
|
|
{timeEntries.filter(e => e.billable).length} billable
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/* Entry rows */}
|
|
|
|
|
<div className="rounded-lg border overflow-hidden">
|
|
|
|
|
{timeEntries.map((entry, idx) => (
|
|
|
|
|
<div key={entry.id}>
|
|
|
|
|
{idx > 0 && <Separator />}
|
|
|
|
|
<div className="px-4 py-3 grid grid-cols-[1fr_auto] gap-4 items-start">
|
|
|
|
|
<div className="space-y-1 min-w-0">
|
|
|
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
|
|
|
{entry.resource_name && (
|
|
|
|
|
<span className="inline-flex items-center gap-1 text-sm font-medium">
|
|
|
|
|
<User className="w-3.5 h-3.5 text-muted-foreground" />
|
|
|
|
|
{entry.resource_name}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{entry.billable && (
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
<StatusBadge {...billableBadge(true)} />
|
2026-02-20 10:28:15 -05:00
|
|
|
)}
|
|
|
|
|
{entry.approved && (
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
<StatusBadge {...approvedBadge(true)} />
|
2026-02-20 10:28:15 -05:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{entry.notes && (
|
|
|
|
|
<p className="text-sm text-muted-foreground" style={{overflowWrap:'anywhere', wordBreak:'break-word'}}>{entry.notes}</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="shrink-0 flex flex-col items-end gap-1">
|
|
|
|
|
<span className="text-sm font-semibold tabular-nums">
|
|
|
|
|
{parseFloat(entry.hours_worked).toFixed(2)}h
|
|
|
|
|
</span>
|
|
|
|
|
{entry.entry_date && (
|
|
|
|
|
<span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
|
|
|
|
|
<Calendar className="w-3 h-3" />
|
|
|
|
|
{new Date(entry.entry_date).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</TabsContent>
|
|
|
|
|
|
|
|
|
|
{/* ── Notes Tab ── */}
|
|
|
|
|
<TabsContent value="notes" className="flex-1 overflow-y-auto px-6 py-4 mt-0">
|
|
|
|
|
{notesLoading ? (
|
|
|
|
|
<div className="flex items-center justify-center py-12">
|
|
|
|
|
<Loader2 className="w-5 h-5 animate-spin text-muted-foreground" />
|
|
|
|
|
</div>
|
|
|
|
|
) : notes.length === 0 ? (
|
|
|
|
|
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground gap-2">
|
|
|
|
|
<MessageSquare className="w-8 h-8 opacity-30" />
|
|
|
|
|
<p className="text-sm">No notes on this ticket</p>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
{notes.map((note) => {
|
|
|
|
|
return (
|
|
|
|
|
<div key={note.id} className="rounded-lg border p-4 space-y-2">
|
|
|
|
|
<div className="flex items-start justify-between gap-3">
|
|
|
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
|
|
|
{note.creator_name && (
|
|
|
|
|
<span className="inline-flex items-center gap-1 text-sm font-medium">
|
|
|
|
|
<User className="w-3.5 h-3.5 text-muted-foreground" />
|
|
|
|
|
{note.creator_name}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{note.publish != null && (
|
feat(design): nav/visual overhaul — brand layer, /status route, KPI dashboard, TanStack DataTable
Major UI refresh on the nav-design-improvements branch. Drops 2013-era
inline styles and consolidates patterns behind shared primitives.
Foundation
- New Wulf brand layer in app/styles/brand.css repointing --primary to
the standards-guide blue (#0075AD) with utility classes for numerics
(.num / .num-lg / .num-xl), metric labels, surface tints, and the
wolf-mark watermark
- Switch primary face to IBM Plex Sans + IBM Plex Mono via next/font;
Helvetica/Arial stays in the fallback chain for brand fidelity
- Wordmark subtitle changed from "PSA Management System" to
"Operations console" everywhere it appeared
- Tagline footer ("Don't be afraid to cry") on every non-mobile page
Status moved out of /dashboard
- New /status route with integration tiles grouped by category, sync
health table, worker pulse cards (analyzer / RMM / sync scheduler),
token-expiry section, conditional alert banner
- Top-bar StatusIndicator polls integration health every 60s and links
to /status
- INTEGRATIONS_DISABLED env var suppresses operator-disabled
integrations (e.g. SentinelOne) — no failure noise from broken-on-
purpose entries. Aliases supported (sentinelone → s1, etc.)
Dashboard rebuilt around KPIs
- /api/dashboard/overview adds today snapshot (opened, resolved, open
total, SLA breaches) with delta math
- /api/dashboard/trends backs queue × priority heatmap, 30-day volume
area chart, 30-day mean resolution time line chart, today's active
engineers leaderboard
Components
- StatusBadge driven by lib/status-registry.ts (priority, ticket
status, classification, source, company type, publish, active /
yes-no / billable / approved registries)
- StatusLight (8px geometric square, five states, three sizes)
- EmptyState (shared dashed panel with icon + headline + optional CTA)
- KpiCard with delta indicator and tonal left border
- WulfMark (mark / wordmark variants from /public/branding)
- Skeleton helpers (SkeletonRow / Rows / Card / Chart / Header / Table)
Navigation
- Admin flat link → dropdown with seven shortcuts
- New UserMenu (initials avatar, role badge, settings + sign-out)
- Active-route highlight is now a 2px Wulf-blue underline echoing the
PageHeader rule (consistent across flat links and submenu triggers);
active children inside dropdowns use bg-primary/10
- Submenu width is content-driven (min-w 320 / max-w 440, single col)
- Mobile hamburger via Sheet, reuses the same nav config
Pages migrated
- 16 admin sub-pages adopt PageHeader (with accent prop)
- /addigy-devices: shadcn Table + Checkbox; PageHeader; status badges
- 10 raw <table> blocks across admin/sync/* migrated to shadcn Table
- /veeam-analysis migrated to shadcn Table (kept its expansion logic)
- Detail routes (analyzer ticket, analyzer analysis) get breadcrumbs
DataTable
- Rewritten on @tanstack/react-table v8 in manual mode; external API
unchanged so all 10+ data-browser pages keep working
- New optional props for drill-down rows: getRowCanExpand + renderSubRow
Mobile
- Multi-select Popover gets max-w-[calc(100vw-1rem)] and
collisionPadding so dropdowns can't overflow narrow viewports
- CI filter bar wraps and shrinks; stat pill flows below
Docs
- New ARCHITECTURE.md (load-bearing reference for runtime, data flow,
workers, analyzer pipeline, auth, deployment, gotchas)
- New DESIGN.md (tokens, layout, navigation IA, component vocabulary,
rolling backlog of remaining cleanup)
- CLAUDE.md refreshed with pointers to the two new docs and the
INTEGRATIONS_DISABLED operator config note
- shadcn registry registered as project-level MCP server (.mcp.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:33:13 -04:00
|
|
|
<StatusBadge {...publishBadge(Number(note.publish))} />
|
2026-02-20 10:28:15 -05:00
|
|
|
)}
|
|
|
|
|
{note.title && (
|
|
|
|
|
<span className="text-sm font-semibold text-foreground">{note.title}</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{note.create_date_time && (
|
|
|
|
|
<span className="inline-flex items-center gap-1 text-xs text-muted-foreground shrink-0">
|
|
|
|
|
<Calendar className="w-3 h-3" />
|
|
|
|
|
{new Date(note.create_date_time).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })}
|
|
|
|
|
{' '}
|
|
|
|
|
{new Date(note.create_date_time).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{note.description && (
|
|
|
|
|
<div className="text-sm text-muted-foreground whitespace-pre-wrap leading-relaxed border-t pt-2" style={{overflowWrap:'anywhere', wordBreak:'break-word'}}>
|
|
|
|
|
{note.description}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</TabsContent>
|
|
|
|
|
|
feat: dual-tab DetailModal with Formatted and Raw views
- Formatted tab: grouped sections (Overview, Parties, Classification, Dates, System)
- ID-to-label resolution for status, priority, source, queue, company type
- Elegant bordered table layout with muted label column
- Clickable phone/URL fields, monospace IDs, formatted dates
- Description rendered in its own block for tickets
- Raw tab: all fields with monospace keys, copy-on-hover
- Auto-detects ticket vs company data for appropriate grouping
- Shadcn Tabs, Separator, Badge used throughout
2026-02-19 21:00:33 -05:00
|
|
|
{/* ── Raw Tab ── */}
|
|
|
|
|
<TabsContent value="raw" className="flex-1 overflow-y-auto px-6 py-4 mt-0">
|
|
|
|
|
<div className="rounded-lg border overflow-hidden">
|
|
|
|
|
{rawFields.map((field, idx) => {
|
|
|
|
|
const value = data[field.key];
|
|
|
|
|
const stringValue = value !== null && value !== undefined ? String(value) : '';
|
|
|
|
|
return (
|
|
|
|
|
<div key={field.key}>
|
|
|
|
|
{idx > 0 && <Separator />}
|
|
|
|
|
<div className="group grid grid-cols-[220px_1fr] items-start">
|
|
|
|
|
<div className="px-4 py-2.5 bg-muted/40 text-xs font-mono text-muted-foreground border-r">
|
|
|
|
|
{field.key}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="px-4 py-2.5 flex items-start justify-between gap-2 min-w-0">
|
|
|
|
|
<div className="flex-1 min-w-0 break-words">
|
|
|
|
|
{field.render ? field.render(value) : renderRaw(value)}
|
|
|
|
|
</div>
|
|
|
|
|
{stringValue && (
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
className="h-6 w-6 opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
|
|
|
|
|
onClick={() => copyToClipboard(stringValue, `raw-${field.key}`)}
|
|
|
|
|
>
|
|
|
|
|
{copiedField === `raw-${field.key}`
|
|
|
|
|
? <CheckCircle2 className="w-3.5 h-3.5 text-green-500" />
|
|
|
|
|
: <Copy className="w-3.5 h-3.5" />}
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</TabsContent>
|
|
|
|
|
</Tabs>
|
2025-11-19 14:18:16 -05:00
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
}
|