nav activa con activeTab, SectionCards dinámicas, fondo light 0.95 oklch
This commit is contained in:
@@ -23,6 +23,14 @@ import {
|
|||||||
SidebarFooter,
|
SidebarFooter,
|
||||||
SidebarHeader,
|
SidebarHeader,
|
||||||
} from "@/components/ui/sidebar"
|
} from "@/components/ui/sidebar"
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
activeTab?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:activeTab': [value: string]
|
||||||
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -34,8 +42,14 @@ import {
|
|||||||
</div>
|
</div>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<NavMain />
|
<NavMain
|
||||||
<NavDocuments />
|
:active-tab="activeTab"
|
||||||
|
@update:active-tab="emit('update:activeTab', $event)"
|
||||||
|
/>
|
||||||
|
<NavDocuments
|
||||||
|
:active-tab="activeTab"
|
||||||
|
@update:active-tab="emit('update:activeTab', $event)"
|
||||||
|
/>
|
||||||
<NavSecondary :items="[]" class="mt-auto" />
|
<NavSecondary :items="[]" class="mt-auto" />
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ const documentItems = [
|
|||||||
{ name: 'nav.wordAssistant', icon: IconFileWord, id: 'word-assistant' },
|
{ name: 'nav.wordAssistant', icon: IconFileWord, id: 'word-assistant' },
|
||||||
{ name: 'nav.templates', icon: IconFileDescription, id: 'templates' },
|
{ name: 'nav.templates', icon: IconFileDescription, id: 'templates' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
activeTab?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:activeTab': [value: string]
|
||||||
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -33,11 +41,12 @@ const documentItems = [
|
|||||||
<SidebarGroupLabel>{{ t('nav.documents') }}</SidebarGroupLabel>
|
<SidebarGroupLabel>{{ t('nav.documents') }}</SidebarGroupLabel>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem v-for="item in documentItems" :key="item.id">
|
<SidebarMenuItem v-for="item in documentItems" :key="item.id">
|
||||||
<SidebarMenuButton as-child>
|
<SidebarMenuButton
|
||||||
<a href="#">
|
:is-active="activeTab === item.id"
|
||||||
<component :is="item.icon" />
|
@click="emit('update:activeTab', item.id)"
|
||||||
<span>{{ t(item.name) }}</span>
|
>
|
||||||
</a>
|
<component :is="item.icon" />
|
||||||
|
<span>{{ t(item.name) }}</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ const mainNavItems = [
|
|||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
items?: { title: string; url: string; icon?: Component }[]
|
items?: { title: string; url: string; icon?: Component }[]
|
||||||
|
activeTab?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:activeTab': [value: string]
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -49,7 +54,11 @@ defineProps<{
|
|||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem v-for="item in mainNavItems" :key="item.id">
|
<SidebarMenuItem v-for="item in mainNavItems" :key="item.id">
|
||||||
<SidebarMenuButton :tooltip="t(item.title)">
|
<SidebarMenuButton
|
||||||
|
:tooltip="t(item.title)"
|
||||||
|
:is-active="activeTab === item.id"
|
||||||
|
@click="emit('update:activeTab', item.id)"
|
||||||
|
>
|
||||||
<component :is="item.icon" v-if="item.icon" />
|
<component :is="item.icon" v-if="item.icon" />
|
||||||
<span>{{ t(item.title) }}</span>
|
<span>{{ t(item.title) }}</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
|
|||||||
@@ -10,95 +10,41 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card"
|
} from "@/components/ui/card"
|
||||||
|
|
||||||
|
interface CardData {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
trend: string
|
||||||
|
up: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
cards?: CardData[]
|
||||||
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
<div class="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
||||||
<Card class="@container/card">
|
<Card v-for="(card, i) in cards" :key="i" class="@container/card">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardDescription>Total Revenue</CardDescription>
|
<CardDescription>{{ card.label }}</CardDescription>
|
||||||
<CardTitle class="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
<CardTitle class="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||||
$1,250.00
|
{{ card.value }}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardAction>
|
<CardAction>
|
||||||
<Badge variant="outline">
|
<Badge variant="outline">
|
||||||
<IconTrendingUp />
|
<IconTrendingUp v-if="card.up" />
|
||||||
+12.5%
|
<IconTrendingDown v-else />
|
||||||
|
{{ card.trend }}
|
||||||
</Badge>
|
</Badge>
|
||||||
</CardAction>
|
</CardAction>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardFooter class="flex-col items-start gap-1.5 text-sm">
|
<CardFooter class="flex-col items-start gap-1.5 text-sm">
|
||||||
<div class="line-clamp-1 flex gap-2 font-medium">
|
<div class="line-clamp-1 flex gap-2 font-medium">
|
||||||
Trending up this month <IconTrendingUp class="size-4" />
|
{{ card.up ? 'Trending up' : 'Trending down' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-muted-foreground">
|
<div class="text-muted-foreground">
|
||||||
Visitors for the last 6 months
|
{{ card.label }}
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card class="@container/card">
|
|
||||||
<CardHeader>
|
|
||||||
<CardDescription>New Customers</CardDescription>
|
|
||||||
<CardTitle class="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
|
||||||
1,234
|
|
||||||
</CardTitle>
|
|
||||||
<CardAction>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<IconTrendingDown />
|
|
||||||
-20%
|
|
||||||
</Badge>
|
|
||||||
</CardAction>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter class="flex-col items-start gap-1.5 text-sm">
|
|
||||||
<div class="line-clamp-1 flex gap-2 font-medium">
|
|
||||||
Down 20% this period <IconTrendingDown class="size-4" />
|
|
||||||
</div>
|
|
||||||
<div class="text-muted-foreground">
|
|
||||||
Acquisition needs attention
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card class="@container/card">
|
|
||||||
<CardHeader>
|
|
||||||
<CardDescription>Active Accounts</CardDescription>
|
|
||||||
<CardTitle class="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
|
||||||
45,678
|
|
||||||
</CardTitle>
|
|
||||||
<CardAction>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<IconTrendingUp />
|
|
||||||
+12.5%
|
|
||||||
</Badge>
|
|
||||||
</CardAction>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter class="flex-col items-start gap-1.5 text-sm">
|
|
||||||
<div class="line-clamp-1 flex gap-2 font-medium">
|
|
||||||
Strong user retention <IconTrendingUp class="size-4" />
|
|
||||||
</div>
|
|
||||||
<div class="text-muted-foreground">
|
|
||||||
Engagement exceed targets
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card class="@container/card">
|
|
||||||
<CardHeader>
|
|
||||||
<CardDescription>Growth Rate</CardDescription>
|
|
||||||
<CardTitle class="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
|
||||||
4.5%
|
|
||||||
</CardTitle>
|
|
||||||
<CardAction>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<IconTrendingUp />
|
|
||||||
+4.5%
|
|
||||||
</Badge>
|
|
||||||
</CardAction>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter class="flex-col items-start gap-1.5 text-sm">
|
|
||||||
<div class="line-clamp-1 flex gap-2 font-medium">
|
|
||||||
Steady performance increase <IconTrendingUp class="size-4" />
|
|
||||||
</div>
|
|
||||||
<div class="text-muted-foreground">
|
|
||||||
Meets growth projections
|
|
||||||
</div>
|
</div>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -149,9 +149,13 @@ const groupedResults = computed(() => {
|
|||||||
<path d="M12 14.3l7.37 -7.37" />
|
<path d="M12 14.3l7.37 -7.37" />
|
||||||
<path d="M12 19.6l8.85 -8.85" />
|
<path d="M12 19.6l8.85 -8.85" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-[18px]">
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4.5">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
|
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
||||||
|
<path d="M12 3l0 18" />
|
||||||
|
<path d="M12 9l4.65 -4.65" />
|
||||||
|
<path d="M12 14.3l7.37 -7.37" />
|
||||||
|
<path d="M12 19.6l8.85 -8.85" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="sr-only">Toggle theme</span>
|
<span class="sr-only">Toggle theme</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
+4
-4
@@ -24,13 +24,13 @@
|
|||||||
--primary: oklch(0.205 0 0);
|
--primary: oklch(0.205 0 0);
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
|
|
||||||
--secondary: oklch(0.97 0 0);
|
--secondary: oklch(0.95 0 0);
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
|
|
||||||
--muted: oklch(0.97 0 0);
|
--muted: oklch(0.95 0 0);
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
|
|
||||||
--accent: oklch(0.97 0 0);
|
--accent: oklch(0.95 0 0);
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
--sidebar-foreground: oklch(0.145 0 0);
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
--sidebar-accent: oklch(0.95 0 0);
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
|||||||
@@ -1,24 +1,139 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue"
|
||||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"
|
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"
|
||||||
import AppSidebar from "@/components/dashboard/AppSidebar.vue"
|
import AppSidebar from "@/components/dashboard/AppSidebar.vue"
|
||||||
import SiteHeader from "@/components/dashboard/SiteHeader.vue"
|
import SiteHeader from "@/components/dashboard/SiteHeader.vue"
|
||||||
|
import SectionCards from "@/components/dashboard/SectionCards.vue"
|
||||||
import DashboardView from "@/views/DashboardView.vue"
|
import DashboardView from "@/views/DashboardView.vue"
|
||||||
|
|
||||||
const sidebarStyle = {
|
const sidebarStyle = {
|
||||||
'--sidebar-width': '16rem',
|
'--sidebar-width': '16rem',
|
||||||
'--header-height': '3rem',
|
'--header-height': '3rem',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeTab = ref('dashboard')
|
||||||
|
|
||||||
|
const tabContent: Record<string, { title: string; description: string; cards: { label: string; value: string; trend: string; up: boolean }[] }> = {
|
||||||
|
dashboard: {
|
||||||
|
title: 'Tablero',
|
||||||
|
description: 'Resumen general del proyecto',
|
||||||
|
cards: [
|
||||||
|
{ label: 'Total Revenue', value: '$1,250.00', trend: '+12.5%', up: true },
|
||||||
|
{ label: 'New Customers', value: '1,234', trend: '-20%', up: false },
|
||||||
|
{ label: 'Active Accounts', value: '45,678', trend: '+12.5%', up: true },
|
||||||
|
{ label: 'Growth Rate', value: '4.5%', trend: '+4.5%', up: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
projects: {
|
||||||
|
title: 'Proyectos',
|
||||||
|
description: 'Gestión de proyectos activos',
|
||||||
|
cards: [
|
||||||
|
{ label: 'Proyectos activos', value: '12', trend: '+3', up: true },
|
||||||
|
{ label: 'Completados', value: '48', trend: '+8', up: true },
|
||||||
|
{ label: 'En pausa', value: '5', trend: '-2', up: false },
|
||||||
|
{ label: 'Por iniciar', value: '7', trend: '+1', up: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
lifecycle: {
|
||||||
|
title: 'Ciclo de Vida',
|
||||||
|
description: 'Seguimiento del ciclo de vida de los proyectos',
|
||||||
|
cards: [
|
||||||
|
{ label: 'En planificación', value: '8', trend: '+2', up: true },
|
||||||
|
{ label: 'En desarrollo', value: '15', trend: '+5', up: true },
|
||||||
|
{ label: 'En revisión', value: '6', trend: '-1', up: false },
|
||||||
|
{ label: 'Entregados', value: '32', trend: '+4', up: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
analytics: {
|
||||||
|
title: 'Analíticas',
|
||||||
|
description: 'Métricas y estadísticas',
|
||||||
|
cards: [
|
||||||
|
{ label: 'Horas registradas', value: '1,240h', trend: '+8%', up: true },
|
||||||
|
{ label: 'HU entregadas', value: '89', trend: '+12%', up: true },
|
||||||
|
{ label: 'Tiempo promedio', value: '3.2d', trend: '-5%', up: true },
|
||||||
|
{ label: 'Satisfacción', value: '92%', trend: '+2%', up: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
team: {
|
||||||
|
title: 'Equipo',
|
||||||
|
description: 'Miembros del equipo y su carga de trabajo',
|
||||||
|
cards: [
|
||||||
|
{ label: 'Miembros', value: '8', trend: '+1', up: true },
|
||||||
|
{ label: 'Asignados', value: '6', trend: '0', up: true },
|
||||||
|
{ label: 'Disponibles', value: '2', trend: '-1', up: false },
|
||||||
|
{ label: 'Productividad', value: '87%', trend: '+3%', up: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
documents: {
|
||||||
|
title: 'Documentos',
|
||||||
|
description: 'Biblioteca de documentos',
|
||||||
|
cards: [
|
||||||
|
{ label: 'Documentos', value: '156', trend: '+12', up: true },
|
||||||
|
{ label: 'Compartidos', value: '43', trend: '+5', up: true },
|
||||||
|
{ label: 'Borradores', value: '18', trend: '-3', up: false },
|
||||||
|
{ label: 'Plantillas', value: '24', trend: '+2', up: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'data-library': {
|
||||||
|
title: 'Biblioteca de Datos',
|
||||||
|
description: 'Repositorio de datos y recursos',
|
||||||
|
cards: [
|
||||||
|
{ label: 'Archivos', value: '89', trend: '+7', up: true },
|
||||||
|
{ label: 'Bases de datos', value: '4', trend: '0', up: true },
|
||||||
|
{ label: 'APIs conectadas', value: '6', trend: '+1', up: true },
|
||||||
|
{ label: 'Storage usado', value: '2.4GB', trend: '+12%', up: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
reports: {
|
||||||
|
title: 'Reportes',
|
||||||
|
description: 'Reportes generados',
|
||||||
|
cards: [
|
||||||
|
{ label: 'Reportes este mes', value: '23', trend: '+8', up: true },
|
||||||
|
{ label: 'Programados', value: '5', trend: '+2', up: true },
|
||||||
|
{ label: 'Exportados', value: '156', trend: '+18', up: true },
|
||||||
|
{ label: 'Pendientes', value: '3', trend: '-1', up: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'word-assistant': {
|
||||||
|
title: 'Asistente Word',
|
||||||
|
description: 'Generación de documentos con IA',
|
||||||
|
cards: [
|
||||||
|
{ label: 'Documentos creados', value: '34', trend: '+6', up: true },
|
||||||
|
{ label: 'Plantillas usadas', value: '12', trend: '+3', up: true },
|
||||||
|
{ label: 'Tokens consumidos', value: '45.2K', trend: '+22%', up: false },
|
||||||
|
{ label: 'Docs pendientes', value: '8', trend: '-2', up: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
templates: {
|
||||||
|
title: 'Plantillas',
|
||||||
|
description: 'Gestión de plantillas',
|
||||||
|
cards: [
|
||||||
|
{ label: 'Plantillas activas', value: '18', trend: '+2', up: true },
|
||||||
|
{ label: 'Usadas este mes', value: '9', trend: '+4', up: true },
|
||||||
|
{ label: 'Categorías', value: '5', trend: '0', up: true },
|
||||||
|
{ label: 'Próximas', value: '3', trend: '+1', up: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SidebarProvider :style="sidebarStyle">
|
<SidebarProvider :style="sidebarStyle">
|
||||||
<AppSidebar variant="inset" />
|
<AppSidebar
|
||||||
|
:active-tab="activeTab"
|
||||||
|
@update:active-tab="activeTab = $event"
|
||||||
|
/>
|
||||||
<SidebarInset>
|
<SidebarInset>
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
<div class="flex flex-1 flex-col">
|
<div class="flex flex-1 flex-col">
|
||||||
<div class="@container/main flex flex-1 flex-col gap-2">
|
<div class="@container/main flex flex-1 flex-col gap-2">
|
||||||
<div class="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
<div class="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
||||||
<DashboardView />
|
<template v-if="activeTab === 'dashboard'">
|
||||||
|
<DashboardView />
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<SectionCards :cards="tabContent[activeTab]?.cards ?? []" />
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user