reorganizar sidebar: Métricas + Proyectos reales de KAPPA, eliminar Tablero duplicado
This commit is contained in:
@@ -1,17 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
IconChartBar,
|
||||
IconDashboard,
|
||||
IconDatabase,
|
||||
IconFileAi,
|
||||
IconFileDescription,
|
||||
IconFolder,
|
||||
IconListDetails,
|
||||
IconReport,
|
||||
IconSettings,
|
||||
IconUsers,
|
||||
} from "@tabler/icons-vue"
|
||||
|
||||
import NavDocuments from "@/components/dashboard/NavDocuments.vue"
|
||||
import NavMain from "@/components/dashboard/NavMain.vue"
|
||||
import NavSecondary from "@/components/dashboard/NavSecondary.vue"
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Component } from "vue"
|
||||
import { useI18n } from "vue-i18n"
|
||||
import {
|
||||
IconCirclePlusFilled,
|
||||
IconDashboard,
|
||||
IconLayoutKanban,
|
||||
IconFolder,
|
||||
IconListDetails,
|
||||
IconChartBar,
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
const { t } = useI18n()
|
||||
|
||||
const mainNavItems = [
|
||||
{ title: 'nav.dashboard', icon: IconDashboard, id: 'dashboard' },
|
||||
{ title: 'nav.board', icon: IconLayoutKanban, id: 'metrics' },
|
||||
{ title: 'nav.projects', icon: IconFolder, id: 'projects' },
|
||||
{ title: 'nav.lifecycle', icon: IconListDetails, id: 'lifecycle' },
|
||||
{ title: 'nav.analytics', icon: IconChartBar, id: 'analytics' },
|
||||
|
||||
@@ -28,7 +28,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const tabLabels: Record<string, string> = {
|
||||
dashboard: 'Tablero',
|
||||
metrics: 'Métricas',
|
||||
projects: 'Proyectos',
|
||||
lifecycle: 'Ciclo de Vida',
|
||||
analytics: 'Analíticas',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"nav": {
|
||||
"quickCreate": "Create project",
|
||||
"dashboard": "Dashboard",
|
||||
"board": "Metrics",
|
||||
"projects": "Projects",
|
||||
"lifecycle": "Lifecycle",
|
||||
"analytics": "Analytics",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"nav": {
|
||||
"quickCreate": "Crear proyecto",
|
||||
"dashboard": "Tablero",
|
||||
"board": "Métricas",
|
||||
"projects": "Proyectos",
|
||||
"lifecycle": "Ciclo de vida",
|
||||
"analytics": "Analíticas",
|
||||
|
||||
@@ -5,28 +5,28 @@ import AppSidebar from "@/components/dashboard/AppSidebar.vue"
|
||||
import SiteHeader from "@/components/dashboard/SiteHeader.vue"
|
||||
import SectionCards from "@/components/dashboard/SectionCards.vue"
|
||||
import DashboardView from "@/views/DashboardView.vue"
|
||||
import ProjectListView from "@/views/ProjectListView.vue"
|
||||
|
||||
const sidebarStyle = {
|
||||
'--sidebar-width': '16rem',
|
||||
'--header-height': '3rem',
|
||||
}
|
||||
|
||||
const activeTab = ref('dashboard')
|
||||
const activeTab = ref('metrics')
|
||||
const viewingProject = ref(false)
|
||||
|
||||
function openProject() {
|
||||
viewingProject.value = true
|
||||
}
|
||||
|
||||
function backToProjects() {
|
||||
viewingProject.value = false
|
||||
}
|
||||
|
||||
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',
|
||||
metrics: {
|
||||
title: 'Métricas',
|
||||
description: 'Indicadores y KPIs generales',
|
||||
cards: [
|
||||
{ label: 'Proyectos activos', value: '12', trend: '+3', up: true },
|
||||
{ label: 'Completados', value: '48', trend: '+8', up: true },
|
||||
@@ -34,6 +34,11 @@ const tabContent: Record<string, { title: string; description: string; cards: {
|
||||
{ label: 'Por iniciar', value: '7', trend: '+1', up: true },
|
||||
],
|
||||
},
|
||||
projects: {
|
||||
title: 'Proyectos',
|
||||
description: 'Proyectos asignados en KAPPA',
|
||||
cards: [],
|
||||
},
|
||||
lifecycle: {
|
||||
title: 'Ciclo de Vida',
|
||||
description: 'Seguimiento del ciclo de vida de los proyectos',
|
||||
@@ -120,17 +125,30 @@ const tabContent: Record<string, { title: string; description: string; cards: {
|
||||
<template>
|
||||
<SidebarProvider :style="sidebarStyle">
|
||||
<AppSidebar
|
||||
:active-tab="activeTab"
|
||||
@update:active-tab="activeTab = $event"
|
||||
:active-tab="viewingProject ? 'projects' : activeTab"
|
||||
@update:active-tab="activeTab = $event; viewingProject = false"
|
||||
/>
|
||||
<SidebarInset>
|
||||
<SiteHeader :active-tab="activeTab" />
|
||||
<SiteHeader :active-tab="viewingProject ? 'projects' : activeTab" />
|
||||
<div class="flex flex-1 flex-col">
|
||||
<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">
|
||||
<template v-if="activeTab === 'dashboard'">
|
||||
<template v-if="viewingProject">
|
||||
<div class="px-4 lg:px-6">
|
||||
<button
|
||||
class="mb-4 inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
@click="backToProjects"
|
||||
>
|
||||
← Volver a Proyectos
|
||||
</button>
|
||||
</div>
|
||||
<DashboardView />
|
||||
</template>
|
||||
<template v-else-if="activeTab === 'projects'">
|
||||
<ProjectListView
|
||||
@select-project="openProject()"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<SectionCards :cards="tabContent[activeTab]?.cards ?? []" />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from "vue"
|
||||
import { useProjectsStore } from "@/stores/projects"
|
||||
import { IconFolder, IconExclamationCircle } from "@tabler/icons-vue"
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
} from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
|
||||
const projects = useProjectsStore()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'select-project': [id: number]
|
||||
}>()
|
||||
|
||||
onMounted(() => {
|
||||
projects.fetchProjects()
|
||||
})
|
||||
|
||||
function getStatusVariant(status?: string) {
|
||||
switch (status) {
|
||||
case 'active': return 'default'
|
||||
case 'completed': return 'secondary'
|
||||
case 'paused': return 'outline'
|
||||
default: return 'default'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-6 px-4 lg:px-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Proyectos</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Proyectos asignados en KAPPA
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline" class="text-sm">
|
||||
{{ projects.count }} proyecto{{ projects.count !== 1 ? 's' : '' }}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<!-- Loading -->
|
||||
<div v-if="projects.loading" class="grid grid-cols-1 gap-4 @lg:grid-cols-2 @3xl:grid-cols-3">
|
||||
<Card v-for="i in 6" :key="i">
|
||||
<CardHeader>
|
||||
<Skeleton class="h-5 w-3/4" />
|
||||
<Skeleton class="h-4 w-1/2" />
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div v-else-if="projects.error" class="flex flex-col items-center gap-3 py-12 text-center">
|
||||
<IconExclamationCircle class="size-10 text-destructive" />
|
||||
<p class="text-lg font-medium">Error al cargar proyectos</p>
|
||||
<p class="text-sm text-muted-foreground">{{ projects.error }}</p>
|
||||
<button
|
||||
class="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
|
||||
@click="projects.fetchProjects()"
|
||||
>
|
||||
Reintentar
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Empty -->
|
||||
<div v-else-if="projects.count === 0" class="flex flex-col items-center gap-3 py-12 text-center">
|
||||
<IconFolder class="size-10 text-muted-foreground" />
|
||||
<p class="text-lg font-medium">Sin proyectos asignados</p>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
No tienes proyectos activos en KAPPA.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Project Grid -->
|
||||
<div v-else class="grid grid-cols-1 gap-4 @lg:grid-cols-2 @3xl:grid-cols-3">
|
||||
<Card
|
||||
v-for="p in projects.projects"
|
||||
:key="p.id"
|
||||
:class="[
|
||||
'cursor-pointer transition-colors hover:border-primary/50',
|
||||
projects.selectedId === p.id ? 'border-primary' : '',
|
||||
]"
|
||||
@click="projects.select(p.id); emit('select-project', p.id)"
|
||||
>
|
||||
<CardHeader>
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<CardTitle class="text-base">
|
||||
{{ p.initiative_name || p.name || `Proyecto ${p.id}` }}
|
||||
</CardTitle>
|
||||
<Badge :variant="getStatusVariant(p.status)">
|
||||
{{ p.status || 'active' }}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardDescription class="line-clamp-2">
|
||||
{{ p.description || 'Sin descripción' }}
|
||||
</CardDescription>
|
||||
<div v-if="p.key" class="flex items-center gap-2 pt-1">
|
||||
<Badge variant="secondary" class="font-mono text-xs">
|
||||
{{ p.key }}
|
||||
</Badge>
|
||||
<span v-if="p.start_date" class="text-xs text-muted-foreground">
|
||||
{{ p.start_date }}
|
||||
</span>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user