- 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.
74 lines
2.6 KiB
Svelte
74 lines
2.6 KiB
Svelte
<script lang="ts">
|
|
import * as Card from '$lib/components/ui/card';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '$lib/components/ui/tabs';
|
|
import type { ChartDataPoint } from '$lib/api/dashboard/types';
|
|
|
|
interface Props {
|
|
monthlyData: ChartDataPoint[];
|
|
}
|
|
|
|
let { monthlyData }: Props = $props();
|
|
|
|
let maxValue = $derived(Math.max(...monthlyData.map((d) => d.value), 1));
|
|
</script>
|
|
|
|
<Card.Root>
|
|
<Card.Header>
|
|
<Card.Title>Tendencia de Operaciones</Card.Title>
|
|
<Card.Description>Evolución mensual de facturas y pedimentos</Card.Description>
|
|
</Card.Header>
|
|
<Card.Content>
|
|
{#if monthlyData.length === 0}
|
|
<div class="text-center text-muted-foreground py-12">
|
|
<p>No hay datos disponibles</p>
|
|
</div>
|
|
{:else}
|
|
<!-- Line Chart Visualization -->
|
|
<div class="h-64 flex items-end justify-between gap-2 border-b border-l p-4">
|
|
{#each monthlyData as point, i}
|
|
<div class="flex-1 flex flex-col items-center gap-2 group">
|
|
<!-- Bar -->
|
|
<div class="w-full flex items-end justify-center" style="height: 200px;">
|
|
<div
|
|
class="w-full max-w-16 bg-primary rounded-t-md transition-all duration-500 hover:bg-primary/80 relative group-hover:shadow-lg"
|
|
style="height: {(point.value / maxValue) * 100}%"
|
|
>
|
|
<!-- Tooltip on hover -->
|
|
<div
|
|
class="absolute -top-8 left-1/2 transform -translate-x-1/2 bg-popover text-popover-foreground px-2 py-1 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap shadow-md border"
|
|
>
|
|
{point.value.toLocaleString()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Label -->
|
|
<span class="text-xs text-muted-foreground font-medium">{point.label}</span>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
|
|
<!-- Stats Summary -->
|
|
<div class="grid grid-cols-3 gap-4 mt-4 pt-4 border-t">
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold text-primary">
|
|
{monthlyData.reduce((sum, d) => sum + d.value, 0).toLocaleString()}
|
|
</div>
|
|
<div class="text-xs text-muted-foreground mt-1">Total</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold text-primary">
|
|
{Math.round(
|
|
monthlyData.reduce((sum, d) => sum + d.value, 0) / monthlyData.length
|
|
).toLocaleString()}
|
|
</div>
|
|
<div class="text-xs text-muted-foreground mt-1">Promedio</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold text-primary">{maxValue.toLocaleString()}</div>
|
|
<div class="text-xs text-muted-foreground mt-1">Máximo</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</Card.Content>
|
|
</Card.Root>
|