- Updated kiosk stats API with expanded metrics - New components: company-tickets-card, gauge-chart, service-desk-card, ticket-leaders-card - Updated cycling-display, kpi-card, and ticker components - Added performance.css for kiosk optimizations - Added Wulf logo asset
270 lines
9 KiB
TypeScript
270 lines
9 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import { KpiCard } from './kpi-card';
|
|
import { ServiceDeskCard } from './service-desk-card';
|
|
import { CompanyTicketsCard } from './company-tickets-card';
|
|
import { TicketLeadersCard } from './ticket-leaders-card';
|
|
import {
|
|
AlertTriangle,
|
|
Clock,
|
|
Timer,
|
|
AlertCircle,
|
|
TicketCheck,
|
|
CheckCircle2,
|
|
TrendingUp,
|
|
Briefcase,
|
|
FileText,
|
|
Building2,
|
|
Network,
|
|
Server
|
|
} from 'lucide-react';
|
|
|
|
interface TicketItem {
|
|
ticketNumber: string;
|
|
title: string;
|
|
companyName: string;
|
|
}
|
|
|
|
interface ResourceLeader {
|
|
resourceName: string;
|
|
ticketsClosed: number;
|
|
}
|
|
|
|
interface IssueType {
|
|
issueType: string;
|
|
subIssueType: string;
|
|
count: number;
|
|
}
|
|
|
|
interface KpiStats {
|
|
criticalTickets: number;
|
|
topCritical?: TicketItem[];
|
|
waitingTickets: number;
|
|
topWaiting?: TicketItem[];
|
|
staleTickets: number;
|
|
topStale?: TicketItem[];
|
|
overdueTickets: number;
|
|
topOverdue?: TicketItem[];
|
|
openTickets: number;
|
|
closedToday: number;
|
|
avgClosedLastWeek: number;
|
|
avgClosedLastMonth: number;
|
|
topClosed?: TicketItem[];
|
|
avgResolutionHours: number;
|
|
hoursThisWeek: number;
|
|
activeCompanies: number;
|
|
openQuotes: number;
|
|
topTicketClosers?: ResourceLeader[];
|
|
topManagedIssueTypes?: IssueType[];
|
|
nmsCoverage: number;
|
|
rmmCoverage: number;
|
|
serviceDeskManaged: number;
|
|
serviceDeskTM: number;
|
|
serviceDeskVendor: number;
|
|
hynesTickets: number;
|
|
seubertTickets: number;
|
|
universalTickets: number;
|
|
}
|
|
|
|
interface CyclingDisplayProps {
|
|
stats: KpiStats;
|
|
cycleInterval: number;
|
|
}
|
|
|
|
export function CyclingDisplay({ stats, cycleInterval }: CyclingDisplayProps) {
|
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
|
|
// Define display items - mix of KPI cards and special cards
|
|
const displayItems: Array<{ type: 'serviceDesk' } | { type: 'companyTickets' } | { type: 'ticketLeaders' } | { type: 'kpi'; kpiIndex: number }> = [
|
|
{ type: 'serviceDesk' },
|
|
{ type: 'companyTickets' },
|
|
{ type: 'ticketLeaders' },
|
|
];
|
|
|
|
const kpis = [
|
|
{
|
|
title: 'Critical Tickets',
|
|
value: stats.criticalTickets,
|
|
icon: AlertTriangle,
|
|
color: stats.criticalTickets > 10 ? 'red' : stats.criticalTickets > 5 ? 'yellow' : 'green',
|
|
trend: 'Priority 1-3',
|
|
tickets: stats.topCritical,
|
|
// Mock trend data for now
|
|
lastWeek: Math.max(0, stats.criticalTickets - Math.floor(Math.random() * 5)),
|
|
lastMonth: Math.max(0, stats.criticalTickets - Math.floor(Math.random() * 8)),
|
|
weeklyTrend: stats.criticalTickets > 5 ? 'up' : 'down',
|
|
monthlyTrend: stats.criticalTickets > 8 ? 'up' : 'down',
|
|
},
|
|
{
|
|
title: 'Tickets Waiting Engagement',
|
|
value: stats.waitingTickets,
|
|
icon: Clock,
|
|
color: stats.waitingTickets > 20 ? 'yellow' : 'blue',
|
|
trend: 'Awaiting Response',
|
|
tickets: stats.topWaiting,
|
|
lastWeek: Math.max(0, stats.waitingTickets - Math.floor(Math.random() * 10)),
|
|
lastMonth: Math.max(0, stats.waitingTickets - Math.floor(Math.random() * 15)),
|
|
weeklyTrend: stats.waitingTickets > 15 ? 'up' : 'down',
|
|
monthlyTrend: stats.waitingTickets > 25 ? 'up' : 'down',
|
|
},
|
|
{
|
|
title: 'Stale Tickets',
|
|
value: stats.staleTickets,
|
|
icon: Timer,
|
|
color: stats.staleTickets > 15 ? 'yellow' : 'blue',
|
|
trend: '7+ Days No Activity',
|
|
tickets: stats.topStale,
|
|
lastWeek: Math.max(0, stats.staleTickets - Math.floor(Math.random() * 5)),
|
|
lastMonth: Math.max(0, stats.staleTickets - Math.floor(Math.random() * 10)),
|
|
weeklyTrend: stats.staleTickets > 10 ? 'up' : 'down',
|
|
monthlyTrend: stats.staleTickets > 12 ? 'up' : 'down',
|
|
},
|
|
{
|
|
title: 'Overdue Tickets',
|
|
value: stats.overdueTickets,
|
|
icon: AlertCircle,
|
|
color: stats.overdueTickets > 10 ? 'red' : stats.overdueTickets > 5 ? 'yellow' : 'green',
|
|
trend: 'Past Due Date',
|
|
tickets: stats.topOverdue,
|
|
lastWeek: Math.max(0, stats.overdueTickets - Math.floor(Math.random() * 5)),
|
|
lastMonth: Math.max(0, stats.overdueTickets - Math.floor(Math.random() * 8)),
|
|
weeklyTrend: stats.overdueTickets > 5 ? 'up' : 'down',
|
|
monthlyTrend: stats.overdueTickets > 8 ? 'up' : 'down',
|
|
},
|
|
{
|
|
title: 'Managed Service Tickets',
|
|
value: stats.serviceDeskManaged || 0,
|
|
icon: TicketCheck,
|
|
color: 'blue',
|
|
trend: 'Service Desk Managed',
|
|
lastWeek: Math.max(0, (stats.serviceDeskManaged || 0) - Math.floor(Math.random() * 10)),
|
|
lastMonth: Math.max(0, (stats.serviceDeskManaged || 0) - Math.floor(Math.random() * 15)),
|
|
weeklyTrend: (stats.serviceDeskManaged || 0) > 30 ? 'up' : 'down',
|
|
monthlyTrend: (stats.serviceDeskManaged || 0) > 40 ? 'up' : 'down',
|
|
},
|
|
{
|
|
title: 'Tickets Closed Today',
|
|
value: stats.closedToday,
|
|
icon: CheckCircle2,
|
|
color: 'green',
|
|
trend: 'Last 24 Hours',
|
|
tickets: stats.topClosed,
|
|
lastWeek: stats.avgClosedLastWeek,
|
|
lastMonth: stats.avgClosedLastMonth,
|
|
weeklyTrend: stats.closedToday > stats.avgClosedLastWeek ? 'up' : stats.closedToday < stats.avgClosedLastWeek ? 'down' : 'same',
|
|
monthlyTrend: stats.closedToday > stats.avgClosedLastMonth ? 'up' : stats.closedToday < stats.avgClosedLastMonth ? 'down' : 'same',
|
|
},
|
|
{
|
|
title: 'Avg Resolution Time',
|
|
value: Math.round(stats.avgResolutionHours),
|
|
icon: TrendingUp,
|
|
color: 'purple',
|
|
suffix: 'h',
|
|
trend: 'Last 30 Days',
|
|
lastWeek: Math.max(0, Math.round(stats.avgResolutionHours - Math.random() * 4)),
|
|
lastMonth: Math.max(0, Math.round(stats.avgResolutionHours - Math.random() * 6)),
|
|
weeklyTrend: stats.avgResolutionHours > 24 ? 'up' : 'down',
|
|
monthlyTrend: stats.avgResolutionHours > 30 ? 'up' : 'down',
|
|
},
|
|
{
|
|
title: 'Hours Logged This Week',
|
|
value: Math.round(stats.hoursThisWeek),
|
|
icon: Briefcase,
|
|
color: 'blue',
|
|
suffix: 'h',
|
|
trend: 'Billable Hours',
|
|
lastWeek: Math.max(0, Math.round(stats.hoursThisWeek - Math.random() * 20)),
|
|
lastMonth: Math.max(0, Math.round(stats.hoursThisWeek - Math.random() * 30)),
|
|
weeklyTrend: stats.hoursThisWeek > 100 ? 'up' : 'down',
|
|
monthlyTrend: stats.hoursThisWeek > 120 ? 'up' : 'down',
|
|
},
|
|
{
|
|
title: 'Open Quotes',
|
|
value: stats.openQuotes,
|
|
icon: FileText,
|
|
color: 'yellow',
|
|
trend: 'Pending Approval',
|
|
lastWeek: Math.max(0, stats.openQuotes - Math.floor(Math.random() * 3)),
|
|
lastMonth: Math.max(0, stats.openQuotes - Math.floor(Math.random() * 5)),
|
|
weeklyTrend: stats.openQuotes > 5 ? 'up' : 'down',
|
|
monthlyTrend: stats.openQuotes > 8 ? 'up' : 'down',
|
|
},
|
|
{
|
|
title: 'Active Companies',
|
|
value: stats.activeCompanies,
|
|
icon: Building2,
|
|
color: 'blue',
|
|
trend: 'Total Customers',
|
|
lastWeek: Math.max(0, stats.activeCompanies - Math.floor(Math.random() * 2)),
|
|
lastMonth: Math.max(0, stats.activeCompanies - Math.floor(Math.random() * 3)),
|
|
weeklyTrend: 'same',
|
|
monthlyTrend: 'up',
|
|
},
|
|
];
|
|
|
|
// Add KPI cards to display items
|
|
kpis.forEach((kpi, index) => {
|
|
displayItems.push({ type: 'kpi', kpiIndex: index });
|
|
});
|
|
|
|
useEffect(() => {
|
|
const interval = setInterval(() => {
|
|
setIsTransitioning(true);
|
|
setTimeout(() => {
|
|
setCurrentIndex((prev) => (prev + 1) % displayItems.length);
|
|
setIsTransitioning(false);
|
|
}, 300);
|
|
}, cycleInterval * 1000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [cycleInterval, displayItems.length]);
|
|
|
|
const currentItem = displayItems[currentIndex];
|
|
|
|
return (
|
|
<div className="flex-1 flex items-center justify-center px-4 pb-4 pt-0">
|
|
<div
|
|
className={`w-full max-w-7xl h-[80vh] transition-opacity duration-200 ${
|
|
isTransitioning ? 'opacity-0' : 'opacity-100'
|
|
}`}
|
|
style={{
|
|
transform: 'translateZ(0)',
|
|
backfaceVisibility: 'hidden',
|
|
}}
|
|
>
|
|
{currentItem.type === 'serviceDesk' ? (
|
|
<ServiceDeskCard
|
|
stats={{
|
|
managedCount: stats.serviceDeskManaged,
|
|
tmCount: stats.serviceDeskTM,
|
|
vendorCount: stats.serviceDeskVendor,
|
|
topManagedIssueTypes: stats.topManagedIssueTypes,
|
|
}}
|
|
/>
|
|
) : currentItem.type === 'companyTickets' ? (
|
|
<CompanyTicketsCard
|
|
stats={{
|
|
hynesCount: stats.hynesTickets,
|
|
seubertCount: stats.seubertTickets,
|
|
universalCount: stats.universalTickets,
|
|
}}
|
|
/>
|
|
) : currentItem.type === 'ticketLeaders' ? (
|
|
<TicketLeadersCard leaders={stats.topTicketClosers || []} />
|
|
) : (
|
|
<KpiCard
|
|
title={kpis[currentItem.kpiIndex!].title}
|
|
value={kpis[currentItem.kpiIndex!].value}
|
|
icon={kpis[currentItem.kpiIndex!].icon}
|
|
color={kpis[currentItem.kpiIndex!].color as any}
|
|
trend={kpis[currentItem.kpiIndex!].trend}
|
|
suffix={kpis[currentItem.kpiIndex!].suffix}
|
|
tickets={kpis[currentItem.kpiIndex!].tickets}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|