Files
plantillas-proyectos/frontend/src/lib/components/dashboard/activity-feed.svelte
acazares 3865a53c5c feat(dashboard): add dashboard module for business metrics and statistics
- 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.
2026-01-12 18:09:06 -06:00

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>