74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
|
|
/* ActiveEngineers — top engineers today by hours logged.
|
||
|
|
*
|
||
|
|
* Compact list: name + ticket count + hours bar. Sorted by hours
|
||
|
|
* desc upstream. Empty when no time has been logged yet today. */
|
||
|
|
|
||
|
|
'use client';
|
||
|
|
|
||
|
|
import { Activity } from 'lucide-react';
|
||
|
|
import { EmptyState } from '@/components/ui/empty-state';
|
||
|
|
|
||
|
|
interface Engineer {
|
||
|
|
resourceId: string;
|
||
|
|
name: string;
|
||
|
|
hours: number;
|
||
|
|
ticketsTouched: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface ActiveEngineersProps {
|
||
|
|
data: Engineer[];
|
||
|
|
}
|
||
|
|
|
||
|
|
export function ActiveEngineers({ data }: ActiveEngineersProps) {
|
||
|
|
if (data.length === 0) {
|
||
|
|
return (
|
||
|
|
<EmptyState
|
||
|
|
icon={Activity}
|
||
|
|
title="No time logged today"
|
||
|
|
description="Engineers will appear here as they post time entries."
|
||
|
|
size="sm"
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const max = data.reduce((m, e) => Math.max(m, e.hours), 0) || 1;
|
||
|
|
const totalHours = data.reduce((s, e) => s + e.hours, 0);
|
||
|
|
const totalTickets = data.reduce((s, e) => s + e.ticketsTouched, 0);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-1">
|
||
|
|
{data.map((e) => {
|
||
|
|
const pct = (e.hours / max) * 100;
|
||
|
|
return (
|
||
|
|
<div key={e.resourceId} className="grid grid-cols-[1fr_auto] items-center gap-3 py-1">
|
||
|
|
<div className="min-w-0">
|
||
|
|
<div className="text-sm font-medium truncate">{e.name}</div>
|
||
|
|
<div className="relative h-1 w-full bg-muted rounded-sm mt-1 overflow-hidden">
|
||
|
|
<div
|
||
|
|
className="absolute inset-y-0 left-0 bg-primary/70"
|
||
|
|
style={{ width: `${pct}%` }}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className="text-right shrink-0">
|
||
|
|
<div className="num text-sm">{e.hours.toFixed(1)}h</div>
|
||
|
|
<div className="text-xs text-muted-foreground">
|
||
|
|
<span className="num">{e.ticketsTouched}</span>{' '}
|
||
|
|
ticket{e.ticketsTouched === 1 ? '' : 's'}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
<div className="border-t pt-2 mt-2 flex justify-between text-xs text-muted-foreground">
|
||
|
|
<span>Total today</span>
|
||
|
|
<span>
|
||
|
|
<span className="num">{totalHours.toFixed(1)}h</span>{' '}
|
||
|
|
across <span className="num">{totalTickets}</span>{' '}
|
||
|
|
ticket{totalTickets === 1 ? '' : 's'}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|