gantt
Gantt chart with quarter roadmap and rollups
Loading preview…
src/gantt/c-gantt-3.vue
<!--
ПОРТ c-gantt-3 ("Gantt chart with quarter roadmap and rollups").
Оригинал берёт императивный доступ через `apiRef`; наш `Gantt.vue` его не
реализует (`defineExpose` не добавлен, см. заголовок Gantt.vue). Тот же
доступ получается напрямую: `AddInitiativeButton` — крошечный локальный
компонент, смонтированный ВНУТРИ `<Gantt>` (в слоте `GanttToolbar`), поэтому
его `useGantt()` находит provide/inject-контекст так же, как `GanttNav`/
`GanttToolbar` уже делают. Родительские workstream-строки не получают
своих баров — `summaryBars` (по умолчанию `true`) сворачивает дочерние
initiative-бары в сводную полосу на родительской строке, как в оригинале.
Дата фиксирована явно в UTC (никаких `new Date()`), тот же якорь, что и в
`ui-gantt`.
-->
<script setup lang="ts">
import { defineComponent, h, ref } from "vue"
import { addDays, startOfDay, startOfWeek } from "date-fns"
import { Gantt, GanttNav, GanttToolbar, GanttView, useGantt } from "@/components/reui/gantt"
import type { GanttEvent, GanttResource } from "@/components/reui/gantt"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
const ANCHOR = new Date(Date.UTC(2024, 0, 8, 0, 0, 0))
const RESOURCES: GanttResource[] = [
{
id: "platform",
title: "Platform",
children: [
{ id: "auth-revamp", title: "Auth Revamp" },
{ id: "api-v2", title: "API v2" },
],
},
{
id: "growth",
title: "Growth",
children: [
{ id: "onboarding", title: "Onboarding Flow" },
{ id: "referrals", title: "Referral Program" },
],
},
{
id: "design-system",
title: "Design System",
children: [
{ id: "tokens", title: "Design Tokens" },
{ id: "components", title: "Component Library" },
],
},
{
id: "backlog",
title: "Backlog",
children: [
{ id: "search", title: "Search Revamp" },
{ id: "billing", title: "Billing v2" },
{ id: "mobile", title: "Mobile App" },
],
},
]
const BACKLOG: Array<{ id: string; title: string; color: string }> = [
{ id: "search", title: "Search Revamp", color: "var(--color-rose-500)" },
{ id: "billing", title: "Billing v2", color: "var(--color-amber-500)" },
{ id: "mobile", title: "Mobile App", color: "var(--color-cyan-500)" },
]
function buildBars(anchor: Date): GanttEvent[] {
const week = startOfWeek(startOfDay(anchor), { weekStartsOn: 0 })
const day = (dayOffset: number) => addDays(week, dayOffset)
const bar = (resourceId: string, title: string, startOffset: number, days: number, color: string, progress?: number): GanttEvent => ({
id: `bar-${resourceId}`,
title,
start: day(startOffset),
end: day(startOffset + days),
allDay: true,
color,
resourceId,
progress,
})
return [
bar("auth-revamp", "Auth Revamp", -30, 60, "var(--color-blue-500)", 100),
bar("api-v2", "API v2", 20, 90, "var(--color-sky-500)", 30),
bar("onboarding", "Onboarding Flow", -20, 60, "var(--color-emerald-500)", 80),
bar("referrals", "Referral Program", 50, 90, "var(--color-teal-500)", 0),
bar("tokens", "Design Tokens", -60, 70, "var(--color-violet-500)", 100),
bar("components", "Component Library", 0, 120, "var(--color-purple-500)", 45),
]
}
const bars = buildBars(ANCHOR)
const scheduled = ref(0)
const AddInitiativeButton = defineComponent({
setup() {
const instance = useGantt<unknown>()
const addInitiative = () => {
const scheduledIds = new Set(instance.api.getEvents().map((event) => event.id))
const index = BACKLOG.findIndex((item) => !scheduledIds.has(`bar-${item.id}`))
if (index === -1) return
const item = BACKLOG[index]!
const week = startOfWeek(startOfDay(ANCHOR), { weekStartsOn: 0 })
const start = addDays(week, 12 + index * 20)
instance.api.addEvent({
id: `bar-${item.id}`,
title: item.title,
start,
end: addDays(start, 24),
allDay: true,
color: item.color,
resourceId: item.id,
})
scheduled.value++
}
return () =>
h(
Button,
{ variant: "outline", size: "sm", disabled: scheduled.value >= BACKLOG.length, onClick: addInitiative },
() => [
h(
"svg",
{
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",
"aria-hidden": "true",
},
[h("path", { d: "M5 12h14" }), h("path", { d: "M12 5v14" })]
),
"Add to roadmap",
]
)
},
})
</script>
<template>
<div class="w-full p-4">
<Card class="w-full py-0">
<CardContent class="p-0">
<Gantt :date="ANCHOR"
initial-center="anchor" :default-events="bars" :resources="RESOURCES" default-scale="quarter" :infinite-scroll="false" :tree-panel="{ width: 200 }" class="h-[480px] w-full">
<div class="flex flex-wrap items-center gap-2 border-b pe-3">
<GanttNav class="min-w-0 flex-1 border-b-0" />
<GanttToolbar>
<AddInitiativeButton />
</GanttToolbar>
</div>
<GanttView />
</Gantt>
</CardContent>
</Card>
</div>
</template>