Phase 1 — Contract bugs:
- Add progress_pct/budget_hours to ProjectHours schema and compute from ProjectBudget
- Add hours/pct to ToolUsage schema, compute in project_detail endpoint
- Fix ProjectDetailView to use correct nested data shape (data.project.*, data.sessions, etc.)
- Fix GenerateReportIn: rename field date → period_date; update reports router
- Fix tasks list date filter: use Query(alias='date') instead of positional arg
- Fix AzureIntegration/AzureWorkItem types: org→organization, id type, ado_id, sync_enabled
- Fix devops API payload and SettingsView to use organization field
- Fix TaskForm ADO work item selector to use wi.ado_id for display, wi.id for value
- Add light theme CSS variables in :root, keep dark in .dark class
- Remove hardcoded class='dark' from HTML, add theme persistence script
- TopBar: persist dark/light to localStorage on toggle
- DashboardView: switch monthly() → timeline() endpoint for charts
- DOW endpoint: add from/to date range filtering
Phase 2 — Planner:
- Add projects API endpoint and Pinia store
- Add project picker to TaskForm
- Fix ADO work item display (#ado_id not #id)
Phase 3 — Calendar:
- getWeekDays() accepts weekLength 5|7 parameter
- Calendar store: add weekLength ref, setWeekLength(), update fetchCurrentView range
- CalendarToolbar: add 5d/7d toggle buttons; fix dateLabel to use days[days.length-1]
- CalendarView: clicking session block navigates to project-detail/:id/:date
- project-detail route: add optional :date? param; ProjectDetailView filters by date
- DnD resize: send start_at alongside end_at (PlannedBlockIn requires both fields)
Phase 4 — AI session summaries:
- Add ai_title/ai_result columns to Session model
- Alembic migration 0006 for new columns
- New ai_session_summary service using Claude Haiku
- Session summarize endpoint: POST /api/dashboard/sessions/{id}/summarize
- Scheduler job: summarize sessions without ai_title every 10 minutes
- SessionOut schema: add ai_title/ai_result fields
- ProjectDetailView: show ai_title as primary, ai_result as subtitle; sparkle button to generate
Phase 5 — Expanded AI assistant:
- Add 14 new tools: list/create/update/delete/complete tasks, prioritize_day,
schedule_task, auto_schedule_day, list_projects, list/delete manual entries,
generate_report, search_sessions, list_work_items
- Import PlannedBlock and AzureWorkItem in assistant service
- Update SYSTEM_PROMPT to describe full agent capabilities
- Agentic loop: 5 → 10 rounds max
- AssistantWidget: add tool labels for all new tools, update quick hints
New files: DevopsView.vue, projects store/API, ai_session_summary.py, migration 0006
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
101 lines
3.7 KiB
Vue
101 lines
3.7 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { dashboardApi } from '@/api/endpoints/dashboard'
|
|
import Card from '@/components/ui/Card.vue'
|
|
import CardContent from '@/components/ui/CardContent.vue'
|
|
import Progress from '@/components/ui/Progress.vue'
|
|
import Spinner from '@/components/ui/Spinner.vue'
|
|
import { formatDuration, formatDate } from '@/lib/utils'
|
|
import type { ProjectSummary } from '@/types'
|
|
|
|
const router = useRouter()
|
|
const projects = ref<ProjectSummary[]>([])
|
|
const loading = ref(false)
|
|
|
|
onMounted(async () => {
|
|
loading.value = true
|
|
try {
|
|
const res = await dashboardApi.projects({})
|
|
projects.value = res.data.sort((a, b) => b.total_hours - a.total_hours)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
})
|
|
|
|
const progressColor = (pct: number | null) => {
|
|
if (!pct) return 'default'
|
|
if (pct > 90) return 'danger'
|
|
if (pct > 70) return 'warning'
|
|
return 'success'
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="p-6">
|
|
<h2 class="text-lg font-semibold text-foreground mb-6">Projects</h2>
|
|
|
|
<div v-if="loading" class="flex items-center justify-center h-40">
|
|
<Spinner size="lg" class="text-primary" />
|
|
</div>
|
|
|
|
<div v-else-if="projects.length === 0" class="text-center text-muted-foreground py-12">
|
|
No projects found
|
|
</div>
|
|
|
|
<div v-else class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
|
<Card
|
|
v-for="proj in projects"
|
|
:key="proj.project_id"
|
|
class="cursor-pointer hover:border-primary/50 transition-colors"
|
|
@click="router.push(`/projects/${proj.project_id}`)"
|
|
>
|
|
<CardContent class="p-4">
|
|
<!-- Header -->
|
|
<div class="flex items-start justify-between gap-2 mb-3">
|
|
<div class="min-w-0">
|
|
<p class="font-semibold text-sm text-foreground truncate">{{ proj.display_name }}</p>
|
|
<p v-if="proj.client" class="text-xs text-muted-foreground truncate">{{ proj.client }}</p>
|
|
</div>
|
|
<span
|
|
v-if="proj.job_number"
|
|
class="text-xs bg-muted text-muted-foreground px-1.5 py-0.5 rounded shrink-0"
|
|
>
|
|
{{ proj.job_number }}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Stats -->
|
|
<div class="space-y-1.5">
|
|
<div class="flex items-center justify-between text-xs">
|
|
<span class="text-muted-foreground">Total hours</span>
|
|
<span class="font-medium text-foreground">{{ formatDuration(proj.total_hours) }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between text-xs">
|
|
<span class="text-muted-foreground">Sessions</span>
|
|
<span class="text-foreground">{{ proj.session_count }}</span>
|
|
</div>
|
|
<div v-if="proj.last_active" class="flex items-center justify-between text-xs">
|
|
<span class="text-muted-foreground">Last active</span>
|
|
<span class="text-foreground">{{ formatDate(proj.last_active) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Budget progress -->
|
|
<div v-if="proj.progress_pct !== null" class="mt-3">
|
|
<div class="flex items-center justify-between text-xs mb-1">
|
|
<span class="text-muted-foreground">Budget</span>
|
|
<span :class="proj.progress_pct > 90 ? 'text-red-400' : 'text-muted-foreground'">
|
|
{{ (proj.progress_pct ?? 0).toFixed(0) }}%
|
|
</span>
|
|
</div>
|
|
<Progress
|
|
:value="proj.progress_pct"
|
|
:color="progressColor(proj.progress_pct)"
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</template>
|