feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
2026-04-29 09:16:46 -04:00
|
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
|
|
|
|
import { Badge } from '@/components/ui/badge';
|
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
|
import { Progress } from '@/components/ui/progress';
|
|
|
|
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
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 {
|
|
|
|
|
|
Table,
|
|
|
|
|
|
TableBody,
|
|
|
|
|
|
TableCell,
|
|
|
|
|
|
TableHead,
|
|
|
|
|
|
TableHeader,
|
|
|
|
|
|
TableRow,
|
|
|
|
|
|
} from '@/components/ui/table';
|
feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
2026-04-29 09:16:46 -04:00
|
|
|
|
import {
|
|
|
|
|
|
BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell,
|
|
|
|
|
|
} from 'recharts';
|
|
|
|
|
|
import {
|
|
|
|
|
|
Brain, Play, RefreshCw, CheckCircle2, AlertTriangle, Clock,
|
|
|
|
|
|
WifiOff, ChevronDown, ChevronRight, Wrench, RotateCcw, Sparkles, Zap, BookOpen, GraduationCap,
|
|
|
|
|
|
} from 'lucide-react';
|
|
|
|
|
|
|
|
|
|
|
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
interface Stats {
|
|
|
|
|
|
total_analyzed: string;
|
|
|
|
|
|
total_ytd: string;
|
|
|
|
|
|
avg_hours: string;
|
|
|
|
|
|
same_day_pct: string;
|
|
|
|
|
|
preventable_pct: string;
|
|
|
|
|
|
offline_pct: string;
|
|
|
|
|
|
auto_resolved_pct: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface CategoryRow { problem_category: string; count: string; avg_hours: string; same_day_pct: string }
|
|
|
|
|
|
interface ResolutionRow { resolution_type: string; count: string }
|
|
|
|
|
|
interface SkillRow { skill: string; count: string }
|
|
|
|
|
|
interface ComplexityRow { complexity: string; count: string }
|
|
|
|
|
|
|
|
|
|
|
|
interface AnalysisData {
|
|
|
|
|
|
stats: Stats;
|
|
|
|
|
|
by_category: CategoryRow[];
|
|
|
|
|
|
by_resolution: ResolutionRow[];
|
|
|
|
|
|
skills: SkillRow[];
|
|
|
|
|
|
by_complexity: ComplexityRow[];
|
|
|
|
|
|
tickets: TicketRow[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
limit: number;
|
|
|
|
|
|
page: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface TicketRow {
|
|
|
|
|
|
ticket_number: string;
|
|
|
|
|
|
company_name: string | null;
|
|
|
|
|
|
device_hostname: string | null;
|
|
|
|
|
|
ticket_created_at: string | null;
|
|
|
|
|
|
ticket_closed_at: string | null;
|
|
|
|
|
|
same_day_close: boolean;
|
|
|
|
|
|
hours_worked: string;
|
|
|
|
|
|
problem_category: string;
|
|
|
|
|
|
resolution_type: string;
|
|
|
|
|
|
complexity: string;
|
|
|
|
|
|
device_was_offline: boolean | null;
|
|
|
|
|
|
backup_completed_before_tech: boolean | null;
|
|
|
|
|
|
preventable: boolean | null;
|
|
|
|
|
|
work_summary: string | null;
|
|
|
|
|
|
recommended_procedure: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface RunStatus {
|
|
|
|
|
|
is_running: boolean;
|
|
|
|
|
|
run_total: number;
|
|
|
|
|
|
run_done: number;
|
|
|
|
|
|
run_errors: number;
|
|
|
|
|
|
total_analyzed: number;
|
|
|
|
|
|
total_eligible: number;
|
|
|
|
|
|
last_analyzed_at: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Config ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
const CATEGORY_CFG: Record<string, { label: string; color: string }> = {
|
|
|
|
|
|
device_offline: { label: 'Device Offline', color: '#94a3b8' },
|
|
|
|
|
|
agent_issue: { label: 'Agent Issue', color: '#f97316' },
|
|
|
|
|
|
job_failed: { label: 'Job Failed', color: '#ef4444' },
|
|
|
|
|
|
storage_issue: { label: 'Storage Issue', color: '#f59e0b' },
|
|
|
|
|
|
network_issue: { label: 'Network Issue', color: '#3b82f6' },
|
|
|
|
|
|
authentication: { label: 'Authentication', color: '#8b5cf6' },
|
|
|
|
|
|
software_error: { label: 'Software Error', color: '#ec4899' },
|
|
|
|
|
|
self_resolved: { label: 'Self-Resolved', color: '#22c55e' },
|
|
|
|
|
|
configuration: { label: 'Configuration', color: '#06b6d4' },
|
|
|
|
|
|
other: { label: 'Other', color: '#6b7280' },
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const RESOLUTION_CFG: Record<string, string> = {
|
|
|
|
|
|
no_action_needed: 'No Action Needed',
|
|
|
|
|
|
device_powered_on: 'Device Powered On',
|
|
|
|
|
|
backup_restarted: 'Backup Restarted',
|
|
|
|
|
|
agent_reinstalled: 'Agent Reinstalled',
|
|
|
|
|
|
storage_cleared: 'Storage Cleared',
|
|
|
|
|
|
settings_updated: 'Settings Updated',
|
|
|
|
|
|
escalated: 'Escalated',
|
|
|
|
|
|
other: 'Other',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const COMPLEXITY_COLOR: Record<string, string> = {
|
|
|
|
|
|
trivial: '#22c55e',
|
|
|
|
|
|
low: '#84cc16',
|
|
|
|
|
|
medium: '#f59e0b',
|
|
|
|
|
|
high: '#ef4444',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
interface SummaryAnalysis {
|
|
|
|
|
|
headline: string;
|
|
|
|
|
|
key_findings: string[];
|
|
|
|
|
|
issue_breakdown: { category: string; insight: string; sop: string }[];
|
|
|
|
|
|
skills_assessment: string;
|
|
|
|
|
|
quick_wins: string[];
|
|
|
|
|
|
automation_opportunities: string;
|
|
|
|
|
|
training_priority: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface SummaryData {
|
|
|
|
|
|
analysis: SummaryAnalysis;
|
|
|
|
|
|
model: string;
|
|
|
|
|
|
generated_at: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function catLabel(k: string) { return CATEGORY_CFG[k]?.label ?? k; }
|
|
|
|
|
|
function catColor(k: string) { return CATEGORY_CFG[k]?.color ?? '#6b7280'; }
|
|
|
|
|
|
function resLabel(k: string) { return RESOLUTION_CFG[k] ?? k; }
|
|
|
|
|
|
|
|
|
|
|
|
function timeAgo(d: string | null) {
|
|
|
|
|
|
if (!d) return '—';
|
|
|
|
|
|
const h = Math.floor((Date.now() - new Date(d).getTime()) / 3_600_000);
|
|
|
|
|
|
if (h < 1) return 'Just now';
|
|
|
|
|
|
if (h < 24) return `${h}h ago`;
|
|
|
|
|
|
return `${Math.floor(h / 24)}d ago`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Expandable ticket row ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
function TicketRow({ t, categoryFilter, onFilter }: {
|
|
|
|
|
|
t: TicketRow;
|
|
|
|
|
|
categoryFilter: string;
|
|
|
|
|
|
onFilter: (cat: string) => void;
|
|
|
|
|
|
}) {
|
|
|
|
|
|
const [open, setOpen] = useState(false);
|
|
|
|
|
|
const catCfg = CATEGORY_CFG[t.problem_category];
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
<TableRow
|
|
|
|
|
|
className="cursor-pointer text-xs align-middle"
|
feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
2026-04-29 09:16:46 -04:00
|
|
|
|
onClick={() => setOpen(o => !o)}
|
|
|
|
|
|
>
|
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
|
|
|
|
<TableCell className="w-6 pl-3">
|
feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
2026-04-29 09:16:46 -04:00
|
|
|
|
{open
|
|
|
|
|
|
? <ChevronDown className="h-3 w-3 text-muted-foreground" />
|
|
|
|
|
|
: <ChevronRight className="h-3 w-3 text-muted-foreground" />}
|
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
|
|
|
|
</TableCell>
|
|
|
|
|
|
<TableCell className="num font-medium">{t.ticket_number}</TableCell>
|
|
|
|
|
|
<TableCell className="max-w-[140px] truncate">{t.company_name ?? '—'}</TableCell>
|
|
|
|
|
|
<TableCell className="num text-[11px]">{t.device_hostname ?? '—'}</TableCell>
|
|
|
|
|
|
<TableCell>
|
feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
2026-04-29 09:16:46 -04:00
|
|
|
|
<Badge
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
style={{ borderColor: catCfg?.color, color: catCfg?.color }}
|
|
|
|
|
|
className="text-[10px] h-4 px-1.5 whitespace-nowrap"
|
|
|
|
|
|
>
|
|
|
|
|
|
{catLabel(t.problem_category)}
|
|
|
|
|
|
</Badge>
|
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
|
|
|
|
</TableCell>
|
|
|
|
|
|
<TableCell className="text-muted-foreground">{resLabel(t.resolution_type)}</TableCell>
|
|
|
|
|
|
<TableCell className="text-center">
|
feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
2026-04-29 09:16:46 -04:00
|
|
|
|
{t.same_day_close
|
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
|
|
|
|
? <CheckCircle2 className="h-3.5 w-3.5 text-emerald-500 mx-auto" />
|
feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
2026-04-29 09:16:46 -04:00
|
|
|
|
: <span className="text-muted-foreground">—</span>}
|
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
|
|
|
|
</TableCell>
|
|
|
|
|
|
<TableCell className="text-right num">{parseFloat(t.hours_worked).toFixed(2)}h</TableCell>
|
|
|
|
|
|
<TableCell>
|
feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
2026-04-29 09:16:46 -04:00
|
|
|
|
<Badge
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
style={{ borderColor: COMPLEXITY_COLOR[t.complexity] ?? '#6b7280', color: COMPLEXITY_COLOR[t.complexity] ?? '#6b7280' }}
|
|
|
|
|
|
className="text-[10px] h-4 px-1.5"
|
|
|
|
|
|
>
|
|
|
|
|
|
{t.complexity}
|
|
|
|
|
|
</Badge>
|
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
|
|
|
|
</TableCell>
|
|
|
|
|
|
<TableCell className="text-muted-foreground num">{timeAgo(t.ticket_created_at)}</TableCell>
|
|
|
|
|
|
</TableRow>
|
feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
2026-04-29 09:16:46 -04:00
|
|
|
|
{open && (
|
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
|
|
|
|
<TableRow className="bg-muted/5">
|
|
|
|
|
|
<TableCell colSpan={10} className="px-8 pb-3 pt-2">
|
feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
2026-04-29 09:16:46 -04:00
|
|
|
|
<div className="grid grid-cols-2 gap-4 text-xs">
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
{t.work_summary && (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span className="text-muted-foreground uppercase tracking-wide text-[10px] font-medium">Summary</span>
|
|
|
|
|
|
<p className="mt-0.5">{t.work_summary}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<div className="flex flex-wrap gap-3 text-muted-foreground">
|
|
|
|
|
|
{t.device_was_offline != null && (
|
|
|
|
|
|
<span className="flex items-center gap-1">
|
|
|
|
|
|
<WifiOff className="h-3 w-3" />
|
|
|
|
|
|
{t.device_was_offline ? 'Device was offline' : 'Device was online'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{t.backup_completed_before_tech != null && (
|
|
|
|
|
|
<span className="flex items-center gap-1">
|
|
|
|
|
|
{t.backup_completed_before_tech
|
|
|
|
|
|
? <><CheckCircle2 className="h-3 w-3 text-green-500" /> Backup auto-completed</>
|
|
|
|
|
|
: <><Wrench className="h-3 w-3" /> Tech action required</>}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{t.preventable != null && (
|
|
|
|
|
|
<span className={t.preventable ? 'text-amber-500' : ''}>
|
|
|
|
|
|
{t.preventable ? '⚠ Preventable' : '✓ Not preventable'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{t.recommended_procedure && (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span className="text-muted-foreground uppercase tracking-wide text-[10px] font-medium">Recommended SOP</span>
|
|
|
|
|
|
<p className="mt-0.5 text-muted-foreground italic">{t.recommended_procedure}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
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
|
|
|
|
</TableCell>
|
|
|
|
|
|
</TableRow>
|
feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
2026-04-29 09:16:46 -04:00
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Page ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
export default function VeeamAnalysisPage() {
|
|
|
|
|
|
const [data, setData] = useState<AnalysisData | null>(null);
|
|
|
|
|
|
const [status, setStatus] = useState<RunStatus | null>(null);
|
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
const [catFilter, setCatFilter] = useState('');
|
|
|
|
|
|
const [page, setPage] = useState(1);
|
|
|
|
|
|
const [summary, setSummary] = useState<SummaryData | null>(null);
|
|
|
|
|
|
const [summaryLoading, setSummaryLoading] = useState(false);
|
|
|
|
|
|
const [summaryError, setSummaryError] = useState<string | null>(null);
|
|
|
|
|
|
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
const fetchData = useCallback(async (cat = catFilter, p = page) => {
|
|
|
|
|
|
const params = new URLSearchParams({ page: String(p) });
|
|
|
|
|
|
if (cat) params.set('category', cat);
|
|
|
|
|
|
const res = await fetch(`/api/veeam/ticket-analysis?${params}`);
|
|
|
|
|
|
setData(await res.json());
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}, [catFilter, page]);
|
|
|
|
|
|
|
|
|
|
|
|
const fetchStatus = useCallback(async () => {
|
|
|
|
|
|
const res = await fetch('/api/veeam/ticket-analysis/status');
|
|
|
|
|
|
const s: RunStatus = await res.json();
|
|
|
|
|
|
setStatus(s);
|
|
|
|
|
|
return s;
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { fetchData(); fetchStatus(); }, []);
|
|
|
|
|
|
|
|
|
|
|
|
const startPolling = useCallback(() => {
|
|
|
|
|
|
if (pollRef.current) return;
|
|
|
|
|
|
pollRef.current = setInterval(async () => {
|
|
|
|
|
|
const s = await fetchStatus();
|
|
|
|
|
|
if (!s.is_running) {
|
|
|
|
|
|
clearInterval(pollRef.current!);
|
|
|
|
|
|
pollRef.current = null;
|
|
|
|
|
|
fetchData();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 2000);
|
|
|
|
|
|
}, [fetchStatus, fetchData]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => () => { if (pollRef.current) clearInterval(pollRef.current); }, []);
|
|
|
|
|
|
|
|
|
|
|
|
const handleRun = async (reanalyze = false) => {
|
|
|
|
|
|
const res = await fetch('/api/veeam/ticket-analysis/run', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify({ reanalyze }),
|
|
|
|
|
|
});
|
|
|
|
|
|
const json = await res.json();
|
|
|
|
|
|
if (json.started) {
|
|
|
|
|
|
await fetchStatus();
|
|
|
|
|
|
startPolling();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSummary = async () => {
|
|
|
|
|
|
setSummaryLoading(true);
|
|
|
|
|
|
setSummaryError(null);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch('/api/veeam/ticket-analysis/summary', { method: 'POST' });
|
|
|
|
|
|
const json = await res.json();
|
|
|
|
|
|
if (json.error) setSummaryError(json.raw ? `${json.error}\n\n${json.raw}` : json.error);
|
|
|
|
|
|
else setSummary(json);
|
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
setSummaryError(e.message);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSummaryLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleCatFilter = (cat: string) => {
|
|
|
|
|
|
const next = catFilter === cat ? '' : cat;
|
|
|
|
|
|
setCatFilter(next);
|
|
|
|
|
|
setPage(1);
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
fetchData(next, 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const totalPages = data ? Math.ceil(data.total / (data.limit ?? 50)) : 1;
|
|
|
|
|
|
const runPct = status?.is_running && status.run_total > 0
|
|
|
|
|
|
? Math.round(100 * status.run_done / status.run_total)
|
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="container mx-auto px-6 py-6 space-y-6">
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<div className="flex items-start justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h1 className="text-2xl font-bold flex items-center gap-2">
|
|
|
|
|
|
<Brain className="h-6 w-6" />
|
|
|
|
|
|
Veeam Backup — Ticket Analysis
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
|
|
|
|
AI classification of YTD Veeam tickets to identify failure patterns, required skills, and SOP gaps.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
{status && !status.is_running && status.total_analyzed > 0 && (
|
|
|
|
|
|
<Button variant="outline" size="sm" onClick={() => handleRun(true)}>
|
|
|
|
|
|
<RotateCcw className="h-3.5 w-3.5 mr-1" />
|
|
|
|
|
|
Re-analyze all
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
onClick={() => handleRun(false)}
|
|
|
|
|
|
disabled={status?.is_running}
|
|
|
|
|
|
>
|
|
|
|
|
|
{status?.is_running
|
|
|
|
|
|
? <><RefreshCw className="h-3.5 w-3.5 mr-1 animate-spin" />Running…</>
|
|
|
|
|
|
: <><Play className="h-3.5 w-3.5 mr-1" />
|
|
|
|
|
|
{status && status.total_analyzed < status.total_eligible ? 'Continue Analysis' : 'Run Analysis'}
|
|
|
|
|
|
</>}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Progress bar */}
|
|
|
|
|
|
{status?.is_running && runPct !== null && (
|
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
|
<Progress value={runPct} className="h-2" />
|
|
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
|
|
{status.run_done} / {status.run_total} tickets analyzed
|
|
|
|
|
|
{status.run_errors > 0 && ` · ${status.run_errors} errors`}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Status line */}
|
|
|
|
|
|
{status && !status.is_running && (
|
|
|
|
|
|
<p className="text-xs text-muted-foreground -mt-4">
|
|
|
|
|
|
{status.total_analyzed} of {status.total_eligible} eligible tickets analyzed
|
|
|
|
|
|
{status.last_analyzed_at && ` · Last run ${timeAgo(status.last_analyzed_at)}`}
|
|
|
|
|
|
{status.total_eligible === 0 && ' — no Veeam tickets with time entries found YTD'}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{status?.total_analyzed === 0 && !status.is_running ? (
|
|
|
|
|
|
<div className="rounded-md border border-dashed py-16 text-center text-muted-foreground">
|
|
|
|
|
|
<Brain className="h-8 w-8 mx-auto mb-3 opacity-30" />
|
|
|
|
|
|
<p className="text-sm">No analysis data yet.</p>
|
|
|
|
|
|
<p className="text-xs mt-1">Click <strong>Run Analysis</strong> to classify {status.total_eligible} tickets with time entries.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{/* Summary cards */}
|
|
|
|
|
|
{loading && !data ? (
|
|
|
|
|
|
<div className="grid gap-4 md:grid-cols-4">
|
|
|
|
|
|
{[...Array(4)].map((_, i) => <Skeleton key={i} className="h-24" />)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : data && (
|
|
|
|
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
|
|
|
|
{[
|
|
|
|
|
|
{
|
|
|
|
|
|
label: 'Tickets Analyzed',
|
|
|
|
|
|
value: data.stats.total_analyzed,
|
|
|
|
|
|
sub: `of ${data.stats.total_ytd} YTD`,
|
|
|
|
|
|
icon: Brain,
|
|
|
|
|
|
cls: '',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
label: 'Same-Day Close',
|
|
|
|
|
|
value: `${data.stats.same_day_pct ?? 0}%`,
|
|
|
|
|
|
sub: 'Opened & closed same day',
|
|
|
|
|
|
icon: CheckCircle2,
|
|
|
|
|
|
cls: 'text-green-500',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
label: 'Avg Hours/Ticket',
|
|
|
|
|
|
value: `${data.stats.avg_hours ?? 0}h`,
|
|
|
|
|
|
sub: `${data.stats.auto_resolved_pct ?? 0}% auto-resolved before tech`,
|
|
|
|
|
|
icon: Clock,
|
|
|
|
|
|
cls: '',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
label: 'Preventable',
|
|
|
|
|
|
value: `${data.stats.preventable_pct ?? 0}%`,
|
|
|
|
|
|
sub: `${data.stats.offline_pct ?? 0}% device was offline`,
|
|
|
|
|
|
icon: AlertTriangle,
|
|
|
|
|
|
cls: 'text-amber-500',
|
|
|
|
|
|
},
|
|
|
|
|
|
].map(({ label, value, sub, icon: Icon, cls }) => (
|
|
|
|
|
|
<Card key={label}>
|
|
|
|
|
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
|
|
|
|
<CardTitle className="text-sm font-medium">{label}</CardTitle>
|
|
|
|
|
|
<Icon className={`h-4 w-4 ${cls || 'text-muted-foreground'}`} />
|
|
|
|
|
|
</CardHeader>
|
|
|
|
|
|
<CardContent>
|
|
|
|
|
|
<div className={`text-2xl font-bold ${cls}`}>{value}</div>
|
|
|
|
|
|
<p className="text-xs text-muted-foreground">{sub}</p>
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Charts row */}
|
|
|
|
|
|
{data && (
|
|
|
|
|
|
<div className="grid gap-6 lg:grid-cols-2">
|
|
|
|
|
|
{/* Problem categories */}
|
|
|
|
|
|
<Card>
|
|
|
|
|
|
<CardHeader className="pb-2">
|
|
|
|
|
|
<CardTitle className="text-sm font-medium">Problem Categories</CardTitle>
|
|
|
|
|
|
<p className="text-xs text-muted-foreground">Click a bar to filter the ticket list</p>
|
|
|
|
|
|
</CardHeader>
|
|
|
|
|
|
<CardContent>
|
|
|
|
|
|
<ResponsiveContainer width="100%" height={260}>
|
|
|
|
|
|
<BarChart
|
|
|
|
|
|
data={data.by_category.map(r => ({
|
|
|
|
|
|
name: catLabel(r.problem_category),
|
|
|
|
|
|
key: r.problem_category,
|
|
|
|
|
|
count: parseInt(r.count),
|
|
|
|
|
|
avg_hours: parseFloat(r.avg_hours),
|
|
|
|
|
|
}))}
|
|
|
|
|
|
layout="vertical"
|
|
|
|
|
|
margin={{ left: 10, right: 20, top: 0, bottom: 0 }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<XAxis type="number" tick={{ fontSize: 10 }} />
|
|
|
|
|
|
<YAxis type="category" dataKey="name" tick={{ fontSize: 11 }} width={110} />
|
|
|
|
|
|
<Tooltip
|
|
|
|
|
|
contentStyle={{ fontSize: 12 }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Bar dataKey="count" radius={[0, 3, 3, 0]} onClick={(d) => handleCatFilter(String(d.key ?? ''))}>
|
|
|
|
|
|
{data.by_category.map((r, i) => (
|
|
|
|
|
|
<Cell
|
|
|
|
|
|
key={i}
|
|
|
|
|
|
fill={catColor(r.problem_category)}
|
|
|
|
|
|
opacity={catFilter && catFilter !== r.problem_category ? 0.3 : 1}
|
|
|
|
|
|
cursor="pointer"
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</Bar>
|
|
|
|
|
|
</BarChart>
|
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Right column: Resolution + Skills */}
|
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
|
{/* Resolution types */}
|
|
|
|
|
|
<Card>
|
|
|
|
|
|
<CardHeader className="pb-2">
|
|
|
|
|
|
<CardTitle className="text-sm font-medium">Resolution Types</CardTitle>
|
|
|
|
|
|
</CardHeader>
|
|
|
|
|
|
<CardContent>
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
{data.by_resolution.map(r => {
|
|
|
|
|
|
const total = data.by_resolution.reduce((s, x) => s + parseInt(x.count), 0);
|
|
|
|
|
|
const pct = total > 0 ? Math.round(100 * parseInt(r.count) / total) : 0;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={r.resolution_type} className="flex items-center gap-2 text-xs">
|
|
|
|
|
|
<span className="w-36 text-muted-foreground truncate">{resLabel(r.resolution_type)}</span>
|
|
|
|
|
|
<div className="flex-1 bg-muted rounded-full h-1.5">
|
|
|
|
|
|
<div className="bg-primary h-1.5 rounded-full" style={{ width: `${pct}%` }} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="w-8 text-right font-medium">{r.count}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Skills */}
|
|
|
|
|
|
<Card>
|
|
|
|
|
|
<CardHeader className="pb-2">
|
|
|
|
|
|
<CardTitle className="text-sm font-medium">Skills Required</CardTitle>
|
|
|
|
|
|
</CardHeader>
|
|
|
|
|
|
<CardContent>
|
|
|
|
|
|
<div className="flex flex-wrap gap-1.5">
|
|
|
|
|
|
{data.skills.map(s => (
|
|
|
|
|
|
<Badge key={s.skill} variant="secondary" className="text-[11px]">
|
|
|
|
|
|
{s.skill}
|
|
|
|
|
|
<span className="ml-1.5 text-muted-foreground">{s.count}</span>
|
|
|
|
|
|
</Badge>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Sonnet Summary */}
|
|
|
|
|
|
{data && parseInt(data.stats.total_analyzed) > 0 && (
|
|
|
|
|
|
<Card>
|
|
|
|
|
|
<CardHeader className="pb-3 flex flex-row items-center justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
|
|
|
|
<Sparkles className="h-4 w-4 text-blue-500" />
|
|
|
|
|
|
Operations Summary
|
|
|
|
|
|
</CardTitle>
|
|
|
|
|
|
<p className="text-xs text-muted-foreground mt-0.5">
|
|
|
|
|
|
Sonnet analysis of aggregate patterns — issue types, skills, and SOPs
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant={summary ? 'outline' : 'default'}
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
onClick={handleSummary}
|
|
|
|
|
|
disabled={summaryLoading}
|
|
|
|
|
|
>
|
|
|
|
|
|
{summaryLoading
|
|
|
|
|
|
? <><RefreshCw className="h-3.5 w-3.5 mr-1.5 animate-spin" />Analyzing…</>
|
|
|
|
|
|
: summary
|
|
|
|
|
|
? <><RotateCcw className="h-3.5 w-3.5 mr-1.5" />Refresh</>
|
|
|
|
|
|
: <><Sparkles className="h-3.5 w-3.5 mr-1.5" />Generate Summary</>}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</CardHeader>
|
|
|
|
|
|
|
|
|
|
|
|
{summaryError && (
|
|
|
|
|
|
<CardContent>
|
|
|
|
|
|
<p className="text-sm text-destructive">{summaryError}</p>
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{summaryLoading && !summary && (
|
|
|
|
|
|
<CardContent className="space-y-3">
|
|
|
|
|
|
<Skeleton className="h-4 w-3/4" />
|
|
|
|
|
|
<Skeleton className="h-3 w-full" />
|
|
|
|
|
|
<Skeleton className="h-3 w-5/6" />
|
|
|
|
|
|
<Skeleton className="h-3 w-full" />
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{summary && (
|
|
|
|
|
|
<CardContent className="space-y-6 pt-0">
|
|
|
|
|
|
{/* Headline */}
|
|
|
|
|
|
<p className="text-sm font-medium">{summary.analysis.headline}</p>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Key findings */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-2">
|
|
|
|
|
|
Key Findings
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<ul className="space-y-1">
|
|
|
|
|
|
{summary.analysis.key_findings?.map((f, i) => (
|
|
|
|
|
|
<li key={i} className="text-sm flex gap-2">
|
|
|
|
|
|
<span className="text-muted-foreground mt-0.5">•</span>
|
|
|
|
|
|
<span>{f}</span>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Issue breakdown */}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-3">
|
|
|
|
|
|
Issue Breakdown & SOPs
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
|
{summary.analysis.issue_breakdown?.map((item, i) => (
|
|
|
|
|
|
<div key={i} className="rounded-md border px-4 py-3 space-y-1">
|
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
|
<Badge
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
style={{ borderColor: catColor(item.category), color: catColor(item.category) }}
|
|
|
|
|
|
className="text-[10px] h-4 px-1.5"
|
|
|
|
|
|
>
|
|
|
|
|
|
{catLabel(item.category)}
|
|
|
|
|
|
</Badge>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p className="text-sm">{item.insight}</p>
|
|
|
|
|
|
<div className="flex items-start gap-1.5 text-xs text-muted-foreground bg-muted/40 rounded px-2.5 py-1.5">
|
|
|
|
|
|
<BookOpen className="h-3 w-3 mt-0.5 flex-shrink-0" />
|
|
|
|
|
|
<span><span className="font-medium text-foreground">SOP:</span> {item.sop}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Bottom row: skills + quick wins + automation */}
|
|
|
|
|
|
<div className="grid gap-4 md:grid-cols-3">
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
|
|
|
|
|
|
<GraduationCap className="h-3.5 w-3.5" />Skills Assessment
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<p className="text-sm text-muted-foreground">{summary.analysis.skills_assessment}</p>
|
|
|
|
|
|
{summary.analysis.training_priority && (
|
|
|
|
|
|
<p className="text-xs border-l-2 border-blue-500/50 pl-2 text-muted-foreground italic">
|
|
|
|
|
|
{summary.analysis.training_priority}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
|
|
|
|
|
|
<Zap className="h-3.5 w-3.5" />Quick Wins
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<ul className="space-y-1">
|
|
|
|
|
|
{summary.analysis.quick_wins?.map((w, i) => (
|
|
|
|
|
|
<li key={i} className="text-sm flex gap-2">
|
|
|
|
|
|
<span className="text-muted-foreground mt-0.5">→</span>
|
|
|
|
|
|
<span>{w}</span>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
|
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
|
|
|
|
|
|
<Brain className="h-3.5 w-3.5" />Automation Opportunities
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<p className="text-sm text-muted-foreground">{summary.analysis.automation_opportunities}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<p className="text-[10px] text-muted-foreground">
|
|
|
|
|
|
Generated by {summary.model} · {new Date(summary.generated_at).toLocaleString()}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Ticket table */}
|
|
|
|
|
|
{data && (
|
|
|
|
|
|
<Card>
|
|
|
|
|
|
<CardHeader className="pb-2 flex flex-row items-center justify-between">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<CardTitle className="text-sm font-medium">
|
|
|
|
|
|
Tickets
|
|
|
|
|
|
{catFilter && (
|
|
|
|
|
|
<Badge variant="secondary" className="ml-2 text-[10px]">
|
|
|
|
|
|
{catLabel(catFilter)}
|
|
|
|
|
|
<button onClick={() => handleCatFilter(catFilter)} className="ml-1 hover:text-destructive">×</button>
|
|
|
|
|
|
</Badge>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</CardTitle>
|
|
|
|
|
|
<p className="text-xs text-muted-foreground mt-0.5">{data.total} tickets</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Button variant="ghost" size="sm" onClick={() => fetchData()}>
|
|
|
|
|
|
<RefreshCw className="h-3.5 w-3.5" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</CardHeader>
|
|
|
|
|
|
<CardContent className="p-0">
|
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
|
|
|
|
<Table>
|
|
|
|
|
|
<TableHeader className="bg-muted/50">
|
|
|
|
|
|
<TableRow>
|
|
|
|
|
|
<TableHead className="w-6" />
|
|
|
|
|
|
<TableHead>Ticket</TableHead>
|
|
|
|
|
|
<TableHead>Client</TableHead>
|
|
|
|
|
|
<TableHead>Device</TableHead>
|
|
|
|
|
|
<TableHead>Category</TableHead>
|
|
|
|
|
|
<TableHead>Resolution</TableHead>
|
|
|
|
|
|
<TableHead className="text-center">Same-day</TableHead>
|
|
|
|
|
|
<TableHead className="text-right">Hours</TableHead>
|
|
|
|
|
|
<TableHead>Complexity</TableHead>
|
|
|
|
|
|
<TableHead>Age</TableHead>
|
|
|
|
|
|
</TableRow>
|
|
|
|
|
|
</TableHeader>
|
|
|
|
|
|
<TableBody>
|
|
|
|
|
|
{data.tickets.length > 0
|
|
|
|
|
|
? data.tickets.map(t => (
|
|
|
|
|
|
<TicketRow
|
|
|
|
|
|
key={t.ticket_number}
|
|
|
|
|
|
t={t}
|
|
|
|
|
|
categoryFilter={catFilter}
|
|
|
|
|
|
onFilter={handleCatFilter}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))
|
|
|
|
|
|
: (
|
|
|
|
|
|
<TableRow>
|
|
|
|
|
|
<TableCell colSpan={10} className="px-4 py-10 text-center text-sm text-muted-foreground">
|
|
|
|
|
|
No analyzed tickets yet — run the analysis above.
|
|
|
|
|
|
</TableCell>
|
|
|
|
|
|
</TableRow>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</TableBody>
|
|
|
|
|
|
</Table>
|
feat: Veeam RPO analysis, comparison, ticket analysis + company teams table
- Add Veeam RPO analysis page (/veeam-analysis) and comparison page (/veeam-comparison)
- Add API routes: /api/veeam/rpo-analyze, rpo-comparison, rpo-offline-log, ticket-analysis
- Add veeam-rpo-service.ts enhancements (RPO logic, offline detection, comparison)
- Add veeam-analysis-state.ts and rmm-device-resolver.ts services
- Add migrations 065-068: company_teams, veeam_rpo_offline_log, rpo_comparison_tables, veeam_ticket_analysis
- Add backup-status page updates and nav links for new Veeam pages
- Add scripts: deactivate-cis-for-inactive-companies, workstation category updates
- Add docs: mimecast-api-guide, veeam-backup-alerting-recommendation, workstation-backup-overview, ticket-analyzer-prompt
- Minor: webhook-service, entity-sync, entity-mapper, sync-helpers, sync.ts, middleware.ts updates
2026-04-29 09:16:46 -04:00
|
|
|
|
|
|
|
|
|
|
{/* Pagination */}
|
|
|
|
|
|
{totalPages > 1 && (
|
|
|
|
|
|
<div className="flex items-center justify-between px-4 py-3 border-t text-xs text-muted-foreground">
|
|
|
|
|
|
<span>Page {page} of {totalPages}</span>
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="outline" size="sm"
|
|
|
|
|
|
disabled={page <= 1}
|
|
|
|
|
|
onClick={() => { setPage(p => p - 1); fetchData(catFilter, page - 1); }}
|
|
|
|
|
|
>Previous</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="outline" size="sm"
|
|
|
|
|
|
disabled={page >= totalPages}
|
|
|
|
|
|
onClick={() => { setPage(p => p + 1); fetchData(catFilter, page + 1); }}
|
|
|
|
|
|
>Next</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|