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>

Установка

npx shadcn-vue@latest add https://revueui.rootapi.dev/r/c-gantt-4.json

Зависимости реестра

npm-зависимости

  • date-fns

Источник: порт из ReUI (Keenthemes, MIT)