Skip to content

Grid, Export, Rendering

Use the toolbar workbook action to export the task table, inspect the locked task styling, toggle baseline and critical-path visuals, or use Print / PDF for a browser report of mounted viewport content.

Source code
TypeScriptts
import { defineCustomElements } from '@revolist/revogrid/loader';
defineCustomElements();

import { defineGanttToolbar, GanttPlugin, GanttCalendarEditorPlugin, type ProjectSnapshot } from '@revolist/gantt';
import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import {
  SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_COLUMNS_WITH_ROW_SELECT, SHOWCASE_DEPENDENCIES,
  SHOWCASE_RESOURCES, SHOWCASE_TOOLBAR_COLUMNS, STANDARD_CALENDAR,
} from './shared/gantt-project-data';
import { createProductionGanttConfig, createProductionTasks, createProductionPersistence, productionError, productionSourceRows } from './GanttProductionPatternsShared';
import './gantt-production-patterns.css';

export function load(parentSelector: string) {
  const parent = document.querySelector(parentSelector);
  if (!parent) return () => {};
  const root = document.createElement('section');
  const controls = document.createElement('div');
  const status = document.createElement('p');
  const toolbar = document.createElement('div');
  const grid = document.createElement('revo-grid');
  const persistence = createProductionPersistence();
  let readOnly = false;
  let localized = false;
  let large = false;
  let project: ProjectSnapshot | null = null;
  const applyConfig = () => { grid.gantt = createProductionGanttConfig(readOnly, localized, project); };
  const applyProject = () => {
    applyConfig();
    grid.ganttDependencies = project?.dependencies ?? (large ? [] : SHOWCASE_DEPENDENCIES);
    grid.ganttCalendars = project?.calendars ?? [STANDARD_CALENDAR];
    grid.ganttResources = project?.resources ?? SHOWCASE_RESOURCES;
    grid.ganttAssignments = project?.assignments ?? (large ? [] : SHOWCASE_ASSIGNMENTS);
    grid.ganttBaselines = project?.baselines ?? (large ? [] : SHOWCASE_BASELINES);
    grid.source = project ? productionSourceRows(project) : createProductionTasks(large);
  };
  const toggle = (label: string, update: (value: boolean) => void) => {
    const element = document.createElement('label');
    const input = document.createElement('input');
    input.type = 'checkbox';
    input.addEventListener('change', () => update(input.checked));
    element.append(input, document.createTextNode(label));
    return { element, input };
  };
  const run = async (action: () => unknown | Promise<unknown>) => {
    try { await action(); } catch (error) { status.textContent = productionError(error); }
  };
  const button = (label: string, action: () => unknown | Promise<unknown>) => {
    const element = document.createElement('button');
    element.type = 'button';
    element.textContent = label;
    element.className = 'rv-btn';
    element.addEventListener('click', () => { void run(action); });
    return element;
  };
  root.className = 'gantt-production-demo';
  controls.className = 'gantt-production-controls';
  status.className = 'gantt-production-status';
  status.setAttribute('role', 'status');
  status.textContent = 'Save and restore use browser memory only; no server is contacted.';
  toolbar.className = 'gantt-production-toolbar';
  grid.className = 'gantt-production-grid';
  grid.theme = currentTheme().isDark() ? 'darkCompact' : 'compact';
  grid.hideAttribution = true;
  grid.plugins = [GanttPlugin, GanttCalendarEditorPlugin, ExportExcelPlugin, RowSelectPlugin];
  grid.rowSelect = { rowOrder: true };
  grid.columns = [...SHOWCASE_COLUMNS_WITH_ROW_SELECT];
  const largeToggle = toggle('750 tasks', (value) => { large = value; project = null; applyProject(); });
  const restore = button('Restore', async () => {
    project = await persistence.load();
    large = false;
    largeToggle.input.checked = false;
    applyProject();
    status.textContent = 'Restored the saved project, including tasks and project collections.';
  });
  restore.disabled = true;
  const fileInput = document.createElement('input');
  fileInput.type = 'file';
  fileInput.hidden = true;
  fileInput.accept = '.json,application/json';
  fileInput.setAttribute('aria-label', 'Import JSON file');
  fileInput.addEventListener('change', async () => {
    const file = fileInput.files?.[0];
    if (file) await run(async () => {
      project = await persistence.importFile(file);
      large = false;
      largeToggle.input.checked = false;
      applyProject();
      status.textContent = `Imported ${project.tasks.length} tasks from ${file.name}.`;
    });
    fileInput.value = '';
  });
  const options = document.createElement('div');
  options.className = 'gantt-production-options';
  const actions = document.createElement('div');
  actions.className = 'gantt-production-actions';
  options.append(
    toggle('Read only', (value) => { readOnly = value; applyConfig(); }).element,
    toggle('Español dates', (value) => { localized = value; applyConfig(); }).element,
    largeToggle.element,
  );
  actions.append(
    fileInput,
    button('Import JSON', () => fileInput.click()),
    button('JSON', () => persistence.exportFile(grid)),
    button('Save', async () => {
      await persistence.save(grid);
      restore.disabled = false;
      status.textContent = 'Saved the current edited project in browser memory.';
    }),
    restore,
    button('Print', () => persistence.print(grid)),
  );
  controls.append(options, actions);
  root.append(controls, status, toolbar, grid);
  parent.appendChild(root);
  applyProject();
  defineGanttToolbar(toolbar, { grid, columns: SHOWCASE_TOOLBAR_COLUMNS, controls: { baseline: true, calendars: true } });
  return () => { toolbar.textContent = ''; grid.remove(); root.remove(); };
}
Vuevue
<template>
  <section class="gantt-production-demo">
    <div class="gantt-production-controls">
      <div class="gantt-production-options">
        <label><input v-model="readOnly" type="checkbox" />Read only</label>
        <label><input v-model="localized" type="checkbox" />Español dates</label>
        <label><input v-model="large" type="checkbox" @change="project = null" />750 tasks</label>
      </div>
      <div class="gantt-production-actions">
        <input ref="importInput" type="file" hidden accept=".json,application/json" aria-label="Import JSON file" @change="importJson" />
        <button type="button" class="rv-btn" @click="importInput?.click()">Import JSON</button>
        <button type="button" class="rv-btn" @click="run(() => persistence.exportFile(getGrid()))">JSON</button>
        <button type="button" class="rv-btn" @click="save">Save</button>
        <button type="button" class="rv-btn" :disabled="!hasSaved" @click="restore">Restore</button>
        <button type="button" class="rv-btn" @click="run(() => persistence.print(getGrid()))">Print</button>
      </div>
    </div>
    <p class="gantt-production-status" role="status">{{ status }}</p>
    <div ref="toolbarRef" class="gantt-production-toolbar"></div>
    <RevoGrid ref="gridRef" class="gantt-production-grid" hide-attribution :theme="isDark ? 'darkCompact' : 'compact'"
      :plugins="plugins" :row-select.prop="rowSelect" :source="tasks" :columns="columns" :gantt.prop="ganttConfig"
      :gantt-dependencies.prop="dependencies" :gantt-calendars.prop="calendars" :gantt-resources.prop="resources"
      :gantt-assignments.prop="assignments" :gantt-baselines.prop="baselines" />
  </section>
</template>

<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
import RevoGrid from '@revolist/vue3-datagrid';
import { defineGanttToolbar, GanttPlugin, GanttCalendarEditorPlugin, type ProjectSnapshot } from '@revolist/gantt';
import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';
import { currentThemeVue } from '../composables/useRandomData';
import { SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_COLUMNS_WITH_ROW_SELECT, SHOWCASE_DEPENDENCIES, SHOWCASE_RESOURCES, SHOWCASE_TOOLBAR_COLUMNS, STANDARD_CALENDAR } from './shared/gantt-project-data';
import { createProductionGanttConfig, createProductionTasks, createProductionPersistence, productionError, productionSourceRows } from './GanttProductionPatternsShared';
import './gantt-production-patterns.css';
const { isDark } = currentThemeVue();
const plugins = [GanttPlugin, GanttCalendarEditorPlugin, ExportExcelPlugin, RowSelectPlugin];
const rowSelect = { rowOrder: true };
const columns = SHOWCASE_COLUMNS_WITH_ROW_SELECT;
const readOnly = ref(false);
const localized = ref(false);
const large = ref(false);
const project = ref<ProjectSnapshot | null>(null);
const status = ref('Save and restore use browser memory only; no server is contacted.');
const hasSaved = ref(false);
const persistence = createProductionPersistence();
const tasks = computed(() => project.value ? productionSourceRows(project.value) : createProductionTasks(large.value));
const dependencies = computed(() => project.value?.dependencies ?? (large.value ? [] : SHOWCASE_DEPENDENCIES));
const calendars = computed(() => project.value?.calendars ?? [STANDARD_CALENDAR]);
const resources = computed(() => project.value?.resources ?? SHOWCASE_RESOURCES);
const assignments = computed(() => project.value?.assignments ?? (large.value ? [] : SHOWCASE_ASSIGNMENTS));
const baselines = computed(() => project.value?.baselines ?? (large.value ? [] : SHOWCASE_BASELINES));
const ganttConfig = computed(() => createProductionGanttConfig(readOnly.value, localized.value, project.value));
const gridRef = ref<InstanceType<typeof RevoGrid> | HTMLRevoGridElement | null>(null);
const importInput = ref<HTMLInputElement | null>(null);
const toolbarRef = ref<HTMLElement | null>(null);
let frame = 0;
function getGrid(): HTMLRevoGridElement {
  const value = gridRef.value as (InstanceType<typeof RevoGrid> & { $el?: HTMLRevoGridElement }) | HTMLRevoGridElement | null;
  const grid = value && '$el' in value ? value.$el : value;
  if (!(grid instanceof HTMLElement)) throw new Error('The grid is still loading.');
  return grid as HTMLRevoGridElement;
}
async function run(action: () => unknown | Promise<unknown>) {
  try { await action(); } catch (error) { status.value = productionError(error); }
}
async function save() { await run(async () => { await persistence.save(getGrid()); hasSaved.value = true; status.value = 'Saved the current edited project in browser memory.'; }); }
async function restore() { await run(async () => { project.value = await persistence.load(); large.value = false; status.value = 'Restored the saved project, including tasks and project collections.'; }); }
async function importJson(event: Event) {
  const input = event.target as HTMLInputElement;
  const file = input.files?.[0];
  if (file) await run(async () => { project.value = await persistence.importFile(file); large.value = false; status.value = `Imported ${project.value.tasks.length} tasks from ${file.name}.`; });
  input.value = '';
}
function mountToolbar() {
  let grid: HTMLRevoGridElement;
  try { grid = getGrid(); } catch { frame = requestAnimationFrame(mountToolbar); return; }
  if (toolbarRef.value) defineGanttToolbar(toolbarRef.value, { grid, columns: SHOWCASE_TOOLBAR_COLUMNS, controls: { baseline: true, calendars: true } });
}
onMounted(async () => { await nextTick(); mountToolbar(); });
onBeforeUnmount(() => { cancelAnimationFrame(frame); if (toolbarRef.value) toolbarRef.value.textContent = ''; });
</script>
Reacttsx
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { defineGanttToolbar, GanttPlugin, GanttCalendarEditorPlugin, type ProjectSnapshot } from '@revolist/gantt';
import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import {
  SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_COLUMNS_WITH_ROW_SELECT, SHOWCASE_DEPENDENCIES,
  SHOWCASE_RESOURCES, SHOWCASE_TOOLBAR_COLUMNS, STANDARD_CALENDAR,
} from './shared/gantt-project-data';
import { createProductionGanttConfig, createProductionTasks, createProductionPersistence, productionError, productionSourceRows } from './GanttProductionPatternsShared';
import './gantt-production-patterns.css';

export default function GanttProductionPatterns() {
  const gridRef = useRef<HTMLRevoGridElement>(null);
  const importInput = useRef<HTMLInputElement>(null);
  const toolbarRef = useRef<HTMLDivElement>(null);
  const [readOnly, setReadOnly] = useState(false);
  const [localized, setLocalized] = useState(false);
  const [large, setLarge] = useState(false);
  const [project, setProject] = useState<ProjectSnapshot | null>(null);
  const [status, setStatus] = useState('Save and restore use browser memory only; no server is contacted.');
  const [hasSaved, setHasSaved] = useState(false);
  const persistence = useMemo(() => createProductionPersistence(), []);
  const plugins = useMemo(() => [GanttPlugin, GanttCalendarEditorPlugin, ExportExcelPlugin, RowSelectPlugin], []);
  const rowSelect = useMemo(() => ({ rowOrder: true }), []);
  const columns = useMemo(() => SHOWCASE_COLUMNS_WITH_ROW_SELECT, []);
  const tasks = useMemo(() => project ? productionSourceRows(project) : createProductionTasks(large), [project, large]);
  const gantt = useMemo(() => createProductionGanttConfig(readOnly, localized, project), [readOnly, localized, project]);
  const dependencies = useMemo(() => project?.dependencies ?? (large ? [] : SHOWCASE_DEPENDENCIES), [project, large]);
  const calendars = useMemo(() => project?.calendars ?? [STANDARD_CALENDAR], [project]);
  const resources = useMemo(() => project?.resources ?? SHOWCASE_RESOURCES, [project]);
  const assignments = useMemo(() => project?.assignments ?? (large ? [] : SHOWCASE_ASSIGNMENTS), [project, large]);
  const baselines = useMemo(() => project?.baselines ?? (large ? [] : SHOWCASE_BASELINES), [project, large]);
  function getGrid(): HTMLRevoGridElement {
    if (!gridRef.current) throw new Error('The grid is still loading.');
    return gridRef.current;
  }
  async function run(action: () => unknown | Promise<unknown>) {
    try { await action(); } catch (error) { setStatus(productionError(error)); }
  }
  async function save() {
    await run(async () => {
      await persistence.save(getGrid());
      setHasSaved(true);
      setStatus('Saved the current edited project in browser memory.');
    });
  }
  async function restore() {
    await run(async () => {
      setProject(await persistence.load());
      setLarge(false);
      setStatus('Restored the saved project, including tasks and project collections.');
    });
  }
  async function importJson(event: React.ChangeEvent<HTMLInputElement>) {
    const input = event.currentTarget;
    const file = input.files?.[0];
    if (file) await run(async () => {
      const imported = await persistence.importFile(file);
      setProject(imported);
      setLarge(false);
      setStatus(`Imported ${imported.tasks.length} tasks from ${file.name}.`);
    });
    input.value = '';
  }
  useEffect(() => {
    const toolbar = toolbarRef.current;
    if (!toolbar || !gridRef.current) return;
    defineGanttToolbar(toolbar, { grid: gridRef.current, columns: SHOWCASE_TOOLBAR_COLUMNS, controls: { baseline: true, calendars: true } });
    return () => { toolbar.textContent = ''; };
  }, []);
  return <section className="gantt-production-demo">
    <div className="gantt-production-controls">
      <div className="gantt-production-options">
        <label><input type="checkbox" checked={readOnly} onChange={(event) => setReadOnly(event.currentTarget.checked)} />Read only</label>
        <label><input type="checkbox" checked={localized} onChange={(event) => setLocalized(event.currentTarget.checked)} />Español dates</label>
        <label><input type="checkbox" checked={large} onChange={(event) => { setLarge(event.currentTarget.checked); setProject(null); }} />750 tasks</label>
      </div>
      <div className="gantt-production-actions">
        <input ref={importInput} type="file" hidden accept=".json,application/json" aria-label="Import JSON file" onChange={importJson} />
        <button type="button" className="rv-btn" onClick={() => importInput.current?.click()}>Import JSON</button>
        <button type="button" className="rv-btn" onClick={() => { void run(() => persistence.exportFile(getGrid())); }}>JSON</button>
        <button type="button" className="rv-btn" onClick={save}>Save</button>
        <button type="button" className="rv-btn" disabled={!hasSaved} onClick={restore}>Restore</button>
        <button type="button" className="rv-btn" onClick={() => { void run(() => persistence.print(getGrid())); }}>Print</button>
      </div>
    </div>
    <p className="gantt-production-status" role="status">{status}</p>
    <div ref={toolbarRef} className="gantt-production-toolbar" />
    <RevoGrid ref={gridRef} className="gantt-production-grid" theme={currentTheme().isDark() ? 'darkCompact' : 'compact'} hideAttribution
      plugins={plugins} rowSelect={rowSelect} source={tasks} columns={columns} gantt={gantt}
      ganttDependencies={dependencies} ganttCalendars={calendars} ganttResources={resources}
      ganttAssignments={assignments} ganttBaselines={baselines} />
  </section>;
}
Angularts
import { ChangeDetectorRef, inject, AfterViewInit, Component, ElementRef, NO_ERRORS_SCHEMA, OnDestroy, ViewChild, ViewEncapsulation } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import { defineGanttToolbar, GanttPlugin, GanttCalendarEditorPlugin, type ProjectSnapshot } from '@revolist/gantt';
import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';
import { SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_COLUMNS_WITH_ROW_SELECT, SHOWCASE_DEPENDENCIES, SHOWCASE_RESOURCES, SHOWCASE_TOOLBAR_COLUMNS, STANDARD_CALENDAR } from './shared/gantt-project-data';
import { createProductionGanttConfig, createProductionTasks, createProductionPersistence, productionError, productionSourceRows } from './GanttProductionPatternsShared';

@Component({
  selector: 'gantt-production-patterns-grid', standalone: true, schemas: [NO_ERRORS_SCHEMA], imports: [RevoGrid],
  styleUrls: ['./gantt-production-patterns.css'], encapsulation: ViewEncapsulation.None,
  template: `<section class="gantt-production-demo">
    <div class="gantt-production-controls">
      <div class="gantt-production-options">
        <label><input type="checkbox" [checked]="readOnly" (change)="setReadOnly($any($event.target).checked)" />Read only</label>
        <label><input type="checkbox" [checked]="localized" (change)="setLocalized($any($event.target).checked)" />Español dates</label>
        <label><input type="checkbox" [checked]="large" (change)="setLarge($any($event.target).checked)" />750 tasks</label>
      </div>
      <div class="gantt-production-actions">
        <input #importFile type="file" hidden accept=".json,application/json" aria-label="Import JSON file" (change)="importJson($event)" />
        <button type="button" class="rv-btn" (click)="importFile.click()">Import JSON</button>
        <button type="button" class="rv-btn" (click)="exportJson()">JSON</button>
        <button type="button" class="rv-btn" (click)="save()">Save</button>
        <button type="button" class="rv-btn" [disabled]="!hasSaved" (click)="restore()">Restore</button>
        <button type="button" class="rv-btn" (click)="print()">Print</button>
      </div>
    </div>
    <p class="gantt-production-status" role="status">{{ status }}</p>
    <div #toolbar class="gantt-production-toolbar"></div>
    <revo-grid #grid class="gantt-production-grid" [theme]="theme" [hideAttribution]="true" [plugins]="plugins"
      [rowSelect]="rowSelect" [source]="tasks" [columns]="columns" [gantt]="ganttConfig" [ganttDependencies]="dependencies"
      [ganttCalendars]="calendars" [ganttResources]="resources" [ganttAssignments]="assignments" [ganttBaselines]="baselines"></revo-grid>
  </section>`,
})
export class GanttProductionPatternsGridComponent implements AfterViewInit, OnDestroy {
  private readonly changeDetector = inject(ChangeDetectorRef);
  @ViewChild('grid', { read: ElementRef }) gridRef!: ElementRef<HTMLRevoGridElement>;
  @ViewChild('toolbar', { read: ElementRef }) toolbarRef!: ElementRef<HTMLElement>;
  theme = currentTheme().isDark() ? 'darkCompact' : 'compact';
  plugins = [GanttPlugin, GanttCalendarEditorPlugin, ExportExcelPlugin, RowSelectPlugin]; rowSelect = { rowOrder: true }; columns = SHOWCASE_COLUMNS_WITH_ROW_SELECT;
  calendars = [STANDARD_CALENDAR]; resources = [...SHOWCASE_RESOURCES]; assignments = [...SHOWCASE_ASSIGNMENTS]; baselines = [...SHOWCASE_BASELINES];
  readOnly = false; localized = false; large = false; tasks = createProductionTasks(); dependencies = SHOWCASE_DEPENDENCIES;
  project: ProjectSnapshot | null = null;
  status = 'Save and restore use browser memory only; no server is contacted.';
  hasSaved = false;
  private persistence = createProductionPersistence();
  ganttConfig = createProductionGanttConfig();
  private refresh() { this.ganttConfig = createProductionGanttConfig(this.readOnly, this.localized, this.project); }
  private refreshProject() {
    this.tasks = this.project ? productionSourceRows(this.project) : createProductionTasks(this.large);
    this.dependencies = [...(this.project?.dependencies ?? (this.large ? [] : SHOWCASE_DEPENDENCIES))];
    this.calendars = [...(this.project?.calendars ?? [STANDARD_CALENDAR])];
    this.resources = [...(this.project?.resources ?? SHOWCASE_RESOURCES)];
    this.assignments = [...(this.project?.assignments ?? (this.large ? [] : SHOWCASE_ASSIGNMENTS))];
    this.baselines = [...(this.project?.baselines ?? (this.large ? [] : SHOWCASE_BASELINES))];
    this.refresh();
  }
  private getGrid(): HTMLRevoGridElement {
    const grid = this.gridRef?.nativeElement;
    if (!grid) throw new Error('The grid is still loading.');
    return grid;
  }
  private async run(action: () => unknown | Promise<unknown>) {
    try { await action(); } catch (error) { this.status = productionError(error); }
    finally { this.changeDetector.markForCheck(); }
  }
  setReadOnly(value: boolean) { this.readOnly = value; this.refresh(); }
  setLocalized(value: boolean) { this.localized = value; this.refresh(); }
  setLarge(value: boolean) { this.large = value; this.project = null; this.refreshProject(); }
  exportJson() { return this.run(() => this.persistence.exportFile(this.getGrid())); }
  print() { return this.run(() => this.persistence.print(this.getGrid())); }
  save() {
    return this.run(async () => {
      await this.persistence.save(this.getGrid()); this.hasSaved = true;
      this.status = 'Saved the current edited project in browser memory.';
    });
  }
  restore() {
    return this.run(async () => {
      this.project = await this.persistence.load(); this.large = false; this.refreshProject();
      this.status = 'Restored the saved project, including tasks and project collections.';
    });
  }
  async importJson(event: Event) {
    const input = event.target as HTMLInputElement;
    const file = input.files?.[0];
    if (file) await this.run(async () => {
      this.project = await this.persistence.importFile(file); this.large = false; this.refreshProject();
      this.status = `Imported ${this.project.tasks.length} tasks from ${file.name}.`;
    });
    input.value = '';
  }
  ngAfterViewInit() { defineGanttToolbar(this.toolbarRef.nativeElement, { grid: this.gridRef.nativeElement, columns: SHOWCASE_TOOLBAR_COLUMNS, controls: { baseline: true, calendars: true } }); }
  ngOnDestroy() { if (this.toolbarRef?.nativeElement) this.toolbarRef.nativeElement.textContent = ''; }
}
Sharedts
import {
  exportProjectSnapshot,
  parseProjectSnapshotJson,
  type GanttPluginConfig,
  type GanttTaskSourceRow,
  type ProjectSnapshot,
} from '@revolist/gantt';
type ISODateString = import('@revolist/gantt').TaskEntity['startDate'];
import { getGanttScheduleSnapshot } from './recipes/current-schedule-snapshot';
import { ProjectSnapshotRestAdapter } from './recipes/rest-adapter';
import { printGanttContainer } from './recipes/print-pdf-recipe';
import {
  SHOWCASE_ASSIGNMENTS,
  SHOWCASE_BASELINES,
  SHOWCASE_DEPENDENCIES,
  SHOWCASE_GANTT_CONFIG,
  SHOWCASE_RESOURCES,
  SHOWCASE_TASKS,
  STANDARD_CALENDAR,
} from './shared/gantt-project-data';

export function createProductionGanttConfig(readOnly = false, localized = false, project?: ProjectSnapshot | null): GanttPluginConfig {
  return {
    ...SHOWCASE_GANTT_CONFIG,
    ...(project ? { id: project.id, name: project.name, version: project.version, currency: project.currency, timeZone: project.timeZone, primaryCalendarId: project.primaryCalendarId, updatedAt: project.updatedAt, durationSettings: project.durationSettings, calendars: project.calendars } : {}),
    allowTaskCreate: true,
    taskCreateRow: true,
    readOnly,
    labels: localized ? {
      taskMode: { auto: 'Automática', manual: 'Manual' },
      workflowStatus: { 'not-started': 'Sin iniciar', 'in-progress': 'En curso', done: 'Finalizada', blocked: 'Bloqueada' },
    } : undefined,
    dateFormats: localized ? {
      locale: 'es-ES',
      table: { dateStyle: 'medium' },
      tooltip: { dateStyle: 'long' },
    } : undefined,
    visuals: {
      ...SHOWCASE_GANTT_CONFIG.visuals,
      showBaseline: true,
      showTaskLabels: true,
      timeRanges: [{ id: 'release-window', startDate: '2026-02-16', endDate: '2026-02-20', label: 'Release window', color: '#8b5cf633' }],
      milestoneLines: [{ id: 'go-live', date: '2026-02-27', label: 'Go live', color: '#e11d48' }],
      taskMarkerHook: (task) => task.deadlineDate ? [{ id: `deadline-${task.id}`, date: task.deadlineDate, label: 'Deadline' }] : [],
      taskBarColorHook: ({ row }) => row.id === SHOWCASE_TASKS[1].id ? { barColor: '#64748b', className: 'gantt-production-locked' } : undefined,
      taskTooltipFields: ['startDate', 'endDate', 'duration', 'percentDone', 'assignees'],
    },
  } as GanttPluginConfig;
}

export function createProductionTasks(large = false): GanttTaskSourceRow[] {
  if (!large) {
    return SHOWCASE_TASKS.map((task, index) => ({ ...task, ...(index === 1 ? { locked: true } : {}) })) as GanttTaskSourceRow[];
  }

  return Array.from({ length: 750 }, (_, index) => {
    const day = 2 + (index % 20);
    return {
      id: `large-${index + 1}`,
      name: `Virtualized task ${index + 1}`,
      startDate: `2026-02-${String(day).padStart(2, '0')}` as ISODateString,
      duration: 1 + (index % 5),
      durationUnit: 'day',
      percentDone: (index * 7) % 101,
      taskMode: 'manual',
      locked: index % 17 === 0,
    } satisfies GanttTaskSourceRow;
  });
}

export function productionSourceRows(project: ProjectSnapshot): GanttTaskSourceRow[] {
  return project.tasks.map((task) => ({
    ...task,
    durationUnit: task.durationUnit ?? 'hour',
    percentDone: task.progressPercent,
    taskMode: task.manuallyScheduled ? 'manual' : 'auto',
  }));
}

export async function getProductionProject(grid: HTMLRevoGridElement): Promise<ProjectSnapshot> {
  return getGanttScheduleSnapshot(grid);
}

export function downloadProductionSnapshot(project: ProjectSnapshot): void {
  const href = URL.createObjectURL(new Blob([exportProjectSnapshot(project, { space: 2 })], { type: 'application/json' }));
  const link = document.createElement('a');
  link.href = href;
  link.download = 'gantt-project.json';
  link.click();
  setTimeout(() => URL.revokeObjectURL(href), 0);
}

/** A real REST recipe with an injected browser-memory transport; no server is contacted. */
export function createProductionPersistence() {
  let savedJson: string | undefined;
  const adapter = new ProjectSnapshotRestAdapter({
    url: '/example/project',
    fetchImpl: (async (_input, init) => {
      if (init?.method === 'PUT') {
        savedJson = String(init.body);
        return new Response(null, { status: 204 });
      }
      return new Response(savedJson ?? 'No saved project', { status: savedJson ? 200 : 404 });
    }) as typeof fetch,
  });
  return {
    save: async (grid: HTMLRevoGridElement) => adapter.save(await getProductionProject(grid)),
    load: () => adapter.load(),
    importFile: async (file: Pick<File, 'text'>): Promise<ProjectSnapshot> => {
      const parsed = parseProjectSnapshotJson(await file.text());
      if (parsed.ok === false) throw new Error(parsed.error);
      return parsed.project;
    },
    exportFile: async (grid: HTMLRevoGridElement) => downloadProductionSnapshot(await getProductionProject(grid)),
    print: (grid: HTMLRevoGridElement) => {
      const result = printGanttContainer(grid.parentElement, { cleanupDelayMs: 60_000, hideSelectors: ['.gantt-production-controls', '.gantt-production-status', '.gantt-production-toolbar', '.gantt-toolbar', '.rgContextMenu'], metadata: { title: grid.gantt?.name ?? 'Project schedule' } });
      if (!result.ok) throw new Error('The print preview could not be opened.');
    },
  };
}

export function productionError(error: unknown): string {
  return error instanceof Error ? error.message : 'The operation failed.';
}

These helpers package common grid-side Gantt work without coupling your application to internal timeline layout data.

The application recipes below live in the examples source browser. Copy their recipes/ files beside your code before using the relative imports; they are maintained examples rather than published Gantt exports.

import {
createGanttColumnPreset,
createGanttContextMenuExtensionItems,
createTaskStatusIndicators,
mapGanttRowsToExcelExport,
} from '@revolist/gantt';
import {
createGanttExcelExportRecipe,
createGanttPdfExcelReportingRecipe,
createGanttTaskStatusIndicatorRecipe,
} from './recipes/grid-projection-examples';

Use mapGanttRowsToExcelExport() when exporting projected task rows. The mapper uses an allowlist of user-facing fields and skips renderer-only timeline metadata.

const rows = mapGanttRowsToExcelExport(taskRows, project, {
includeResourceRows: false,
});
downloadWorkbook({
sheetName: project.name,
rows,
});

For shells that prefer a recipe object:

const recipe = createGanttExcelExportRecipe({
rows: taskRows,
project,
});
downloadWorkbook({
fields: recipe.fieldNames,
rows: recipe.rows,
});

Use the combined reporting recipe when your app has separate toolbar buttons for stakeholder PDF reports and spreadsheet handoff. PDF uses the browser print dialog, so users can choose Save as PDF. Excel dispatches the Pro export-excel event, so include ExportExcelPlugin with the grid plugins.

import {
GanttPlugin,
} from '@revolist/gantt';
import {
createGanttPdfExcelReportingRecipe,
} from './recipes/grid-projection-examples';
import { ExportExcelPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector<HTMLRevoGridElement>('revo-grid');
const host = document.querySelector<HTMLElement>('#gantt-report');
if (!grid || !host) {
throw new Error('Gantt report target was not found');
}
grid.plugins = [GanttPlugin, ExportExcelPlugin];
grid.gantt = project;
const reportRange = {
startDate: '2026-05-01',
endDate: '2026-05-31',
};
const reporting = createGanttPdfExcelReportingRecipe({
grid,
target: host,
document,
window,
excel: {
workbookName: `${project.name}.xlsx`,
},
});
pdfButton.addEventListener('click', () => {
const result = reporting.exportPdf(project, reportRange);
if (!result.ok) {
showExportError(result.reason);
}
});
excelButton.addEventListener('click', () => {
reporting.exportExcel({
sheetName: 'Tasks',
});
});

For a single “export report” action, call both commands together:

const result = reporting.exportReport(project, {
range: { label: 'Executive status report' },
excel: { sheetName: 'Task register' },
});
if (!result.pdf.ok) {
showExportError(result.pdf.reason);
}

Column presets group the default Gantt task-table columns into common planning views.

grid.columns = createGanttColumnPreset('schedule');
grid.source = [...taskRows];

Available presets include core, schedule, dependencies, resources, baseline, cost, progress, and all.

Use indicator helpers to produce labels and class names for custom cell renderers.

const status = createTaskStatusIndicators({
taskId: row.id,
projectReadOnly,
taskLocked: lockedTaskIds.has(row.id),
});
renderIndicatorCell({
className: status.classNames.join(' '),
title: status.label,
indicators: status.indicators,
});

For multiple rows:

const indicatorRows = createGanttTaskStatusIndicatorRecipe({
rows: taskRows,
projectReadOnly,
lockedTaskIds,
});

Gantt installs separate RevoGrid Pro row and column context menus. Row menus are task-oriented (Add, Convert to milestone, Indent, Outdent, Delete). Main grid column menus expose search, sort, and filter actions. The pinned Gantt canvas column menu exposes timeline zoom controls.

Use the built-in context-menu helpers with rowContextMenu to add duplicate, assign-resource, export, or custom task actions.

const contextMenuItems = createGanttContextMenuExtensionItems({
duplicate: {
copyName: (task) => `${task.name ?? 'Task'} copy`,
},
delete: {
confirm: (context) => window.confirm(`Delete ${context.task?.name ?? 'task'}?`),
onDeleteTask: (context) => {
if (context.taskId) {
deleteTask(context.taskId);
}
},
},
assignResource: {
onAssignResource: (context) => {
if (context.taskId) {
openResourcePicker(context.taskId);
}
},
},
export: {
exportOptions: (context) => ({
workbookName: 'gantt.xlsx',
sheetName: context.task?.name ?? 'Gantt',
}),
},
});
grid.rowContextMenu = {
...grid.rowContextMenu,
items: [...(grid.rowContextMenu?.items ?? []), ...contextMenuItems],
};

Disable individual items with false.

const exportOnly = createGanttContextMenuExtensionItems({
duplicate: false,
delete: false,
assignResource: false,
export: { name: 'Export visible Gantt tasks' },
});

When printing a nested container, the recipe keeps its ancestor path visible and hides sibling application content until cleanup. Printing captures the mounted view; virtualized offscreen rows are not expanded automatically.