feat: add top 3 tickets display on kiosk KPI cards

- Show company name and ticket title for top 3 tickets per category
- Critical tickets sorted by priority then create date
- Waiting tickets sorted by oldest activity
- Stale tickets sorted by oldest activity
- Overdue tickets sorted by most overdue
- Closed tickets sorted by most recent
- Adjusted card layout to accommodate ticket list
This commit is contained in:
root 2026-02-03 08:25:27 -05:00
parent 1d1b4d2a71
commit 724791121a
4 changed files with 146 additions and 5 deletions

View file

@ -17,13 +17,24 @@ import {
Server
} from 'lucide-react';
interface TicketItem {
ticketNumber: string;
title: string;
companyName: string;
}
interface KpiStats {
criticalTickets: number;
topCritical?: TicketItem[];
waitingTickets: number;
topWaiting?: TicketItem[];
staleTickets: number;
topStale?: TicketItem[];
overdueTickets: number;
topOverdue?: TicketItem[];
openTickets: number;
closedToday: number;
topClosed?: TicketItem[];
avgResolutionHours: number;
hoursThisWeek: number;
activeCompanies: number;
@ -48,6 +59,7 @@ export function CyclingDisplay({ stats, cycleInterval }: CyclingDisplayProps) {
icon: AlertTriangle,
color: stats.criticalTickets > 10 ? 'red' : stats.criticalTickets > 5 ? 'yellow' : 'green',
trend: 'Priority 1-3',
tickets: stats.topCritical,
},
{
title: 'Tickets Waiting Engagement',
@ -55,6 +67,7 @@ export function CyclingDisplay({ stats, cycleInterval }: CyclingDisplayProps) {
icon: Clock,
color: stats.waitingTickets > 20 ? 'yellow' : 'blue',
trend: 'Awaiting Response',
tickets: stats.topWaiting,
},
{
title: 'Stale Tickets',
@ -62,6 +75,7 @@ export function CyclingDisplay({ stats, cycleInterval }: CyclingDisplayProps) {
icon: Timer,
color: stats.staleTickets > 15 ? 'yellow' : 'blue',
trend: '7+ Days No Activity',
tickets: stats.topStale,
},
{
title: 'Overdue Tickets',
@ -69,6 +83,7 @@ export function CyclingDisplay({ stats, cycleInterval }: CyclingDisplayProps) {
icon: AlertCircle,
color: stats.overdueTickets > 10 ? 'red' : stats.overdueTickets > 5 ? 'yellow' : 'green',
trend: 'Past Due Date',
tickets: stats.topOverdue,
},
{
title: 'Total Open Tickets',
@ -83,6 +98,7 @@ export function CyclingDisplay({ stats, cycleInterval }: CyclingDisplayProps) {
icon: CheckCircle2,
color: 'green',
trend: 'Last 24 Hours',
tickets: stats.topClosed,
},
{
title: 'Avg Resolution Time',
@ -160,6 +176,7 @@ export function CyclingDisplay({ stats, cycleInterval }: CyclingDisplayProps) {
color={currentKpi.color as any}
trend={currentKpi.trend}
suffix={currentKpi.suffix}
tickets={currentKpi.tickets}
/>
</div>
</div>

View file

@ -2,6 +2,12 @@
import { LucideIcon } from 'lucide-react';
interface TicketItem {
ticketNumber: string;
title: string;
companyName: string;
}
interface KpiCardProps {
title: string;
value: number | string;
@ -9,9 +15,10 @@ interface KpiCardProps {
trend?: string;
color?: 'red' | 'yellow' | 'green' | 'blue' | 'purple';
suffix?: string;
tickets?: TicketItem[];
}
export function KpiCard({ title, value, icon: Icon, trend, color = 'blue', suffix = '' }: KpiCardProps) {
export function KpiCard({ title, value, icon: Icon, trend, color = 'blue', suffix = '', tickets = [] }: KpiCardProps) {
const colorClasses = {
red: 'text-red-500 border-red-500',
yellow: 'text-yellow-500 border-yellow-500',
@ -30,18 +37,40 @@ export function KpiCard({ title, value, icon: Icon, trend, color = 'blue', suffi
return (
<div className={`flex flex-col items-center justify-center h-full w-full p-12 ${bgClasses[color]} border-4 ${colorClasses[color]} rounded-3xl`}>
<Icon className={`w-32 h-32 mb-8 ${colorClasses[color]}`} />
<div className={`text-9xl font-bold mb-4 ${colorClasses[color]}`}>
<Icon className={`w-24 h-24 mb-6 ${colorClasses[color]}`} />
<div className={`text-8xl font-bold mb-3 ${colorClasses[color]}`}>
{value}{suffix}
</div>
<div className="text-4xl text-gray-300 text-center font-semibold mb-2">
<div className="text-3xl text-gray-300 text-center font-semibold mb-4">
{title}
</div>
{trend && (
<div className="text-2xl text-gray-500 mt-4">
<div className="text-xl text-gray-500 mb-6">
{trend}
</div>
)}
{tickets && tickets.length > 0 && (
<div className="w-full mt-4 space-y-3">
{tickets.map((ticket, index) => (
<div key={ticket.ticketNumber} className="bg-black/30 rounded-lg p-3 border border-gray-700">
<div className="flex items-start gap-3">
<span className={`text-lg font-bold ${colorClasses[color]} flex-shrink-0`}>
#{ticket.ticketNumber}
</span>
<div className="flex-1 min-w-0">
<div className="text-sm font-semibold text-gray-300 truncate">
{ticket.companyName}
</div>
<div className="text-xs text-gray-400 line-clamp-2">
{ticket.title}
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
);
}