gantt
Gantt chart with weekly capacity board
Gantt chart with weekly capacity board
Загрузка превью…
src/gantt/c-gantt-4.vue
<!--
ПОРТ c-gantt-4 ("Gantt chart with weekly capacity board").
Тот же приём, что и в c-gantt-3.vue: `AddAssignmentButton` — локальный
компонент, смонтированный внутри `<Gantt>`, получает `useGantt()` через
provide/inject вместо отсутствующего `apiRef`/`defineExpose`.
`renderResourceLabel` — реальный view-config колбэк (VNodeChild из
GanttTreeRow.vue): для группы (isGroup) возвращает `undefined`, чтобы
сработал дефолтный текстовый лейбл, для лица — аватар + инициалы. Дата
фиксирована явно в 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 type { GanttColumnContext } from "@/components/reui/gantt"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
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 PIXEL = "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="
const RESOURCES: GanttResource[] = [
{
id: "product-squad",
title: "Product Squad",
children: [
{ id: "ada", title: "Ada Lovelace" },
{ id: "alan", title: "Alan Turing" },
],
},
{
id: "design-squad",
title: "Design Squad",
children: [
{ id: "grace", title: "Grace Hopper" },
{ id: "linus", title: "Linus Torvalds" },
],
},
]
const RESOURCE_META: Record<string, { initials: string; avatar: string }> = {
ada: { initials: "AL", avatar: PIXEL },
alan: { initials: "AT", avatar: PIXEL },
grace: { initials: "GH", avatar: PIXEL },
linus: { initials: "LT", avatar: PIXEL },
}
const PEOPLE = ["ada", "alan", "grace", "linus"]
const TASK_POOL = [
{ title: "Bug triage", color: "var(--color-amber-500)" },
{ title: "Code review", color: "var(--color-rose-500)" },
{ title: "Spec draft", color: "var(--color-teal-500)" },
{ title: "Pairing", color: "var(--color-indigo-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): GanttEvent => ({
id: `bar-${resourceId}`,
title,
start: day(startOffset),
end: day(startOffset + days),
allDay: true,
color,
resourceId,
})
return [
bar("ada", "Checkout API", -1, 3, "var(--color-blue-500)"),
bar("alan", "Search Indexing", 0, 3, "var(--color-sky-500)"),
bar("grace", "Dashboard Redesign", 1, 3, "var(--color-violet-500)"),
bar("linus", "Infra Migration", -2, 3, "var(--color-purple-500)"),
]
}
const bars = buildBars(ANCHOR)
const addedCount = ref(0)
function renderResourceLabel({ resource, isGroup }: GanttColumnContext) {
if (isGroup) return undefined
const person = RESOURCE_META[resource.id]
return h("span", { class: "flex min-w-0 items-center gap-2" }, [
h(Avatar, { class: "size-5" }, () => [
h(AvatarImage, { src: person?.avatar ?? PIXEL, alt: resource.title }),
h(AvatarFallback, { class: "text-[10px]" }, () => person?.initials ?? resource.title.charAt(0)),
]),
h("span", { class: "truncate" }, resource.title),
])
}
const AddAssignmentButton = defineComponent({
setup() {
const instance = useGantt<unknown>()
const addAssignment = () => {
const n = addedCount.value++
const person = PEOPLE[n % PEOPLE.length]!
const task = TASK_POOL[n % TASK_POOL.length]!
const week = startOfWeek(startOfDay(ANCHOR), { weekStartsOn: 0 })
const start = addDays(week, (n % 5) + 1)
instance.api.addEvent({
id: `bar-extra-${n}`,
title: task.title,
start,
end: addDays(start, 2),
allDay: true,
color: task.color,
resourceId: person,
})
}
return () =>
h(
Button,
{ variant: "outline", size: "sm", onClick: addAssignment },
() => [
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 assignment",
]
)
},
})
</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="week"
off-days
:infinite-scroll="false"
:tree-panel="{ width: 220 }"
:render-resource-label="renderResourceLabel"
class="h-[440px] 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>
<AddAssignmentButton />
</GanttToolbar>
</div>
<GanttView />
</Gantt>
</CardContent>
</Card>
</div>
</template>