refactor(dashboard): extract ProjectBreakdown and ToolUsageList

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-05-13 11:09:46 +01:00
parent 0a6ce6c3c4
commit e2f9f35362
2 changed files with 123 additions and 0 deletions

View file

@ -0,0 +1,68 @@
<script setup lang="ts">
import { RouterLink } from 'vue-router'
import Card from '@/components/ui/Card.vue'
import CardHeader from '@/components/ui/CardHeader.vue'
import CardTitle from '@/components/ui/CardTitle.vue'
import CardContent from '@/components/ui/CardContent.vue'
import Progress from '@/components/ui/Progress.vue'
import { formatDuration } from '@/lib/utils'
import type { ProjectSummary } from '@/types'
defineProps<{
data: ProjectSummary[]
loading: boolean
}>()
const progressColor = (pct: number | null) => {
if (!pct) return 'default'
if (pct > 90) return 'danger'
if (pct > 70) return 'warning'
return 'success'
}
</script>
<template>
<Card class="border-border/60 bg-card panel-glow">
<CardHeader class="pb-2">
<CardTitle class="text-xs font-semibold text-muted-foreground uppercase tracking-widest">Projects</CardTitle>
</CardHeader>
<CardContent>
<!-- Loading skeleton -->
<div v-if="loading" class="space-y-3">
<div v-for="i in 5" :key="i" class="space-y-1.5">
<div class="flex justify-between">
<div class="h-3 rounded bg-muted animate-pulse" :style="{ width: `${80 + i * 15}px` }" />
<div class="h-3 w-12 bg-muted animate-pulse rounded" />
</div>
<div class="h-1.5 bg-muted animate-pulse rounded-full" />
</div>
</div>
<!-- Empty state -->
<div v-else-if="data.length === 0" class="flex flex-col items-center justify-center py-8 gap-2">
<svg class="h-8 w-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 7a2 2 0 012-2h3.586a1 1 0 01.707.293l1.414 1.414A1 1 0 0011.414 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" />
</svg>
<p class="text-xs text-muted-foreground">No project data yet</p>
</div>
<!-- List -->
<div v-else class="space-y-2.5">
<RouterLink
v-for="proj in data.slice(0, 8)"
:key="proj.project_id"
:to="`/projects/${proj.project_id}`"
class="block group"
>
<div class="flex items-center justify-between text-xs mb-1">
<span class="text-foreground truncate max-w-[160px] font-medium group-hover:text-primary transition-colors">{{ proj.display_name }}</span>
<span class="text-muted-foreground shrink-0 tabular-nums ml-2">{{ formatDuration(proj.total_hours) }}</span>
</div>
<Progress
v-if="proj.progress_pct !== null"
:value="proj.progress_pct"
:color="progressColor(proj.progress_pct)"
/>
</RouterLink>
</div>
</CardContent>
</Card>
</template>

View file

@ -0,0 +1,55 @@
<script setup lang="ts">
import { computed } from 'vue'
import Card from '@/components/ui/Card.vue'
import CardHeader from '@/components/ui/CardHeader.vue'
import CardTitle from '@/components/ui/CardTitle.vue'
import CardContent from '@/components/ui/CardContent.vue'
import type { ToolUsage } from '@/types'
const props = defineProps<{
data: ToolUsage[]
loading: boolean
}>()
const maxToolPct = computed(() => Math.max(...props.data.map((t) => t.pct), 1))
</script>
<template>
<Card class="border-border/60 bg-card panel-glow">
<CardHeader class="pb-2">
<CardTitle class="text-xs font-semibold text-muted-foreground uppercase tracking-widest">Tool Usage</CardTitle>
</CardHeader>
<CardContent>
<!-- Loading skeleton -->
<div v-if="loading" class="space-y-3">
<div v-for="i in 5" :key="i" class="flex items-center gap-2">
<div class="h-3 rounded bg-muted animate-pulse" :style="{ width: `${40 + i * 10}px` }" />
<div class="flex-1 h-2 bg-muted animate-pulse rounded-full" />
<div class="h-3 w-8 bg-muted animate-pulse rounded" />
</div>
</div>
<!-- Empty state -->
<div v-else-if="data.length === 0" class="flex flex-col items-center justify-center py-8 gap-2">
<svg class="h-8 w-8 text-muted-foreground/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
</svg>
<p class="text-xs text-muted-foreground">No tool data yet</p>
</div>
<!-- List -->
<div v-else class="space-y-2.5">
<div v-for="tool in data.slice(0, 8)" :key="tool.tool" class="flex items-center gap-2.5">
<span class="text-xs text-foreground w-24 truncate shrink-0 tabular-nums">{{ tool.tool }}</span>
<div class="flex-1 h-1.5 bg-muted rounded-full overflow-hidden">
<div
class="h-full bg-primary/70 rounded-full transition-all duration-300"
:style="{ width: `${(tool.pct / maxToolPct) * 100}%` }"
/>
</div>
<span class="text-xs text-muted-foreground w-9 text-right shrink-0 tabular-nums">
{{ (tool.pct ?? 0).toFixed(0) }}%
</span>
</div>
</div>
</CardContent>
</Card>
</template>