Files
plantillas-proyectos/frontend/src/lib/components/dashboard/chart-card.svelte
AlexeerCT 2aab5c8fdc feat: add optional seed data functionality to init_first_time.sh
- Implemented argument parsing to allow for optional loading of example data with --seed-data flag.
- Added functions to seed example data for customs brokers, clients and providers, packages, classes, parts, pedimentos, and invoices.
- Enhanced user feedback during the seeding process with detailed output of the seeded data counts.
- Updated usage instructions in the script header to reflect the new functionality.
2026-03-08 01:15:08 -06:00

81 lines
2.8 KiB
Svelte

<script lang="ts">
import * as Card from '$lib/components/ui/card';
import type { ChartDataPoint } from '$lib/api/dashboard/types';
interface Props {
title: string;
description?: string;
data: ChartDataPoint[];
type?: 'bar' | 'line' | 'pie';
class?: string;
}
let { title, description, data, type = 'bar', class: cls = '' }: Props = $props();
let maxValue = $derived(Math.max(...data.map((d) => d.value), 1));
const rankColors = [
'text-yellow-600 bg-yellow-50 dark:bg-yellow-950/40 border-yellow-200 dark:border-yellow-800',
'text-slate-500 bg-slate-50 dark:bg-slate-800 border-slate-200 dark:border-slate-700',
'text-orange-600 bg-orange-50 dark:bg-orange-950/40 border-orange-200 dark:border-orange-800',
'text-muted-foreground bg-muted border-border',
'text-muted-foreground bg-muted border-border'
];
const barGradients = [
'from-primary to-primary/60',
'from-primary/85 to-primary/50',
'from-primary/70 to-primary/40',
'from-primary/55 to-primary/30',
'from-primary/40 to-primary/20'
];
</script>
<Card.Root class={`overflow-hidden ${cls}`}>
<Card.Header class="pb-3">
<Card.Title class="text-base">{title}</Card.Title>
{#if description}
<Card.Description>{description}</Card.Description>
{/if}
</Card.Header>
<Card.Content>
{#if data.length === 0}
<div class="flex flex-col items-center justify-center py-8 gap-2 text-muted-foreground">
<p class="text-sm">No hay datos disponibles</p>
</div>
{:else if type === 'bar'}
<div class="space-y-3">
{#each data as item, i}
<div class="space-y-1.5">
<div class="flex items-center gap-2 text-sm">
<span
class={`inline-flex items-center justify-center w-5 h-5 rounded text-[10px] font-bold border shrink-0 ${rankColors[i] ?? rankColors[3]}`}
>{i + 1}</span>
<span class="font-medium truncate flex-1">{item.label}</span>
<span class="text-muted-foreground font-medium tabular-nums">{item.value.toLocaleString()}</span>
</div>
<div class="h-1.5 bg-muted rounded-full overflow-hidden">
<div
class={`h-full rounded-full bg-gradient-to-r transition-all duration-700 ${barGradients[i] ?? barGradients[4]}`}
style="width: {(item.value / maxValue) * 100}%"
></div>
</div>
</div>
{/each}
</div>
{:else if type === 'pie'}
<div class="grid grid-cols-2 gap-3">
{#each data as item, i}
<div class="flex items-center gap-2">
<div class="w-2.5 h-2.5 rounded-full bg-primary" style="opacity: {1 - i * 0.15}"></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>