Files
plantillas-proyectos/frontend/src/lib/components/dashboard/chart-card.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

60 lines
1.7 KiB
Svelte

<script lang="ts">
import * as Card from '$lib/components/ui/card';
import type { ChartDataPoint } from '$lib/api/dashboard/types';
import { onMount } from 'svelte';
interface Props {
title: string;
description?: string;
data: ChartDataPoint[];
type?: 'bar' | 'line' | 'pie';
}
let { title, description, data, type = 'bar' }: Props = $props();
let maxValue = $derived(Math.max(...data.map((d) => d.value), 1));
</script>
<Card.Root>
<Card.Header>
<Card.Title>{title}</Card.Title>
{#if description}
<Card.Description>{description}</Card.Description>
{/if}
</Card.Header>
<Card.Content>
{#if data.length === 0}
<div class="text-center text-muted-foreground py-8">No hay datos disponibles</div>
{:else if type === 'bar'}
<div class="space-y-3">
{#each data as item}
<div class="space-y-1">
<div class="flex items-center justify-between text-sm">
<span class="font-medium truncate">{item.label}</span>
<span class="text-muted-foreground">{item.value.toLocaleString()}</span>
</div>
<div class="h-2 bg-muted rounded-full overflow-hidden">
<div
class="h-full bg-primary rounded-full transition-all duration-500"
style="width: {(item.value / maxValue) * 100}%"
></div>
</div>
</div>
{/each}
</div>
{:else if type === 'pie'}
<div class="grid grid-cols-2 gap-4">
{#each data as item}
<div class="flex items-center gap-2">
<div class="w-3 h-3 rounded-full bg-primary"></div>
<div class="flex-1 min-w-0">
<div class="text-sm font-medium truncate">{item.label}</div>
<div class="text-xs text-muted-foreground">{item.value.toLocaleString()}</div>
</div>
</div>
{/each}
</div>
{/if}
</Card.Content>
</Card.Root>