- 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.
60 lines
1.7 KiB
Svelte
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>
|