- Implemented DTOs for KPIs, activity items, chart data points, and dashboard statistics. - Created API routes for fetching dashboard statistics, operations overview, and inventory metrics. - Developed a service layer to handle the logic for generating dashboard statistics, including invoice metrics, client/provider metrics, and recent activity. - Added frontend API client methods for fetching dashboard data. - Created TypeScript types for dashboard data structures. - Developed Svelte components for displaying recent activity, KPI cards, trend charts, and donut charts.
107 lines
2.7 KiB
Svelte
107 lines
2.7 KiB
Svelte
<script lang="ts">
|
|
import * as Card from '$lib/components/ui/card';
|
|
import { Badge } from '$lib/components/ui/badge';
|
|
import type { ActivityItem } from '$lib/api/dashboard/types';
|
|
import {
|
|
FileText,
|
|
Users,
|
|
Package,
|
|
CheckCircle,
|
|
Clock,
|
|
AlertCircle,
|
|
TruckIcon
|
|
} from 'lucide-svelte';
|
|
|
|
interface Props {
|
|
activities: ActivityItem[];
|
|
}
|
|
|
|
let { activities }: Props = $props();
|
|
|
|
const icons = {
|
|
invoice: FileText,
|
|
pedimento: FileText,
|
|
client: Users,
|
|
provider: Users,
|
|
item: Package,
|
|
transport: TruckIcon
|
|
};
|
|
|
|
const statusColors = {
|
|
active: 'default',
|
|
pending: 'secondary',
|
|
completed: 'outline',
|
|
error: 'destructive'
|
|
} as const;
|
|
|
|
function getIcon(type: string) {
|
|
return icons[type as keyof typeof icons] || FileText;
|
|
}
|
|
|
|
function formatDate(dateStr: string): string {
|
|
const date = new Date(dateStr);
|
|
const now = new Date();
|
|
const diffMs = now.getTime() - date.getTime();
|
|
const diffMins = Math.floor(diffMs / 60000);
|
|
const diffHours = Math.floor(diffMs / 3600000);
|
|
const diffDays = Math.floor(diffMs / 86400000);
|
|
|
|
if (diffMins < 60) {
|
|
return `Hace ${diffMins} min`;
|
|
} else if (diffHours < 24) {
|
|
return `Hace ${diffHours}h`;
|
|
} else if (diffDays < 7) {
|
|
return `Hace ${diffDays}d`;
|
|
} else {
|
|
return date.toLocaleDateString('es-MX', { month: 'short', day: 'numeric' });
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<Card.Root>
|
|
<Card.Header>
|
|
<Card.Title>Actividad Reciente</Card.Title>
|
|
<Card.Description>Últimas operaciones registradas en el sistema</Card.Description>
|
|
</Card.Header>
|
|
<Card.Content>
|
|
{#if activities.length === 0}
|
|
<div class="text-center text-muted-foreground py-8">
|
|
<Clock class="h-8 w-8 mx-auto mb-2 opacity-50" />
|
|
<p>No hay actividad reciente</p>
|
|
</div>
|
|
{:else}
|
|
<div class="space-y-4">
|
|
{#each activities as activity}
|
|
{@const Icon = getIcon(activity.type)}
|
|
<div class="flex items-start gap-4">
|
|
<div class="mt-1">
|
|
<div class="p-2 rounded-lg bg-primary/10">
|
|
<Icon class="h-4 w-4 text-primary" />
|
|
</div>
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center justify-between gap-2">
|
|
<p class="text-sm font-medium leading-none">{activity.title}</p>
|
|
<span class="text-xs text-muted-foreground whitespace-nowrap">
|
|
{formatDate(activity.timestamp)}
|
|
</span>
|
|
</div>
|
|
{#if activity.description}
|
|
<p class="text-sm text-muted-foreground mt-1">{activity.description}</p>
|
|
{/if}
|
|
{#if activity.status}
|
|
<Badge
|
|
variant={statusColors[activity.status as keyof typeof statusColors] || 'default'}
|
|
class="mt-2"
|
|
>
|
|
{activity.status}
|
|
</Badge>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</Card.Content>
|
|
</Card.Root>
|