Gantt Chart - Copy this React, Tailwind Component to your project
import { useState, useCallback, useRef } from "react"; import { format, addMonths, addWeeks, startOfWeek, startOfMonth, endOfWeek, endOfMonth, startOfQuarter, endOfQuarter, startOfYear, endOfYear, eachDayOfInterval, eachMonthOfInterval, eachYearOfInterval, addDays, } from "date-fns"; import { BiZoomIn, BiZoomOut } from "react-icons/bi"; import { MdFileDownload } from "react-icons/md"; import html2canvas from "html2canvas"; import dummyGanttData from "./dummyGanttData.json"; type Project = { id: number; building: string; gate: string; startDate: Date; endDate: Date; }; const gates = ["A", "B", "C", "D", "E", "D-C"] as const; type Gate = (typeof gates)[number]; const gateColors: Record<Gate, string> = { A: "bg-blue-500", B: "bg-green-500", C: "bg-purple-500", D: "bg-orange-500", E: "bg-red-500", "D-C": "bg-yellow-500", }; const ProjectTimeline: React.FC = () => { const [zoomLevel, setZoomLevel] = useState<number>(1); const [selectedGate, setSelectedGate] = useState<Gate | "all">("all"); const [selectedBuilding, setSelectedBuilding] = useState<string | "all">( "all" ); const [selectedView, setSelectedView] = useState< "weekly" | "monthly" | "quarterly" | "yearly" | "fiveYear" >("monthly"); const chartRef = useRef<HTMLDivElement>(null); const projects: Project[] = dummyGanttData.map((data: any) => ({ id: data.id, building: data.building, gate: data.gate, startDate: new Date(data.startDate), endDate: new Date(data.endDate), })); const getTimelineData = useCallback(() => { const currentDate = new Date(); switch (selectedView) { case "weekly": const weekStart = startOfWeek(currentDate); const weekEnd = endOfWeek(currentDate); return { title: `${format(currentDate, "yyyy MMMM")} Week ${format( currentDate, "w" )} (${format(weekStart, "d")}-${format(weekEnd, "d")})`, dates: eachDayOfInterval({ start: weekStart, end: weekEnd }), }; case "monthly": const monthStart = startOfMonth(currentDate); const monthEnd = endOfMonth(currentDate); return { title: `${format(currentDate, "yyyy MMMM")} (${format( monthStart, "d" )}-${format(monthEnd, "d")})`, dates: eachDayOfInterval({ start: monthStart, end: monthEnd }), }; case "quarterly": const quarterStart = startOfQuarter(currentDate); const quarterEnd = endOfQuarter(currentDate); return { title: `${format(currentDate, "yyyy")} ${format( quarterStart, "MMMM" )}-${format(quarterEnd, "MMMM")}`, dates: eachMonthOfInterval({ start: quarterStart, end: quarterEnd }), }; case "yearly": const yearStart = startOfYear(currentDate); const yearEnd = endOfYear(currentDate); return { title: `${format(currentDate, "yyyy")} (Jan-Dec)`, dates: eachMonthOfInterval({ start: yearStart, end: yearEnd }), }; case "fiveYear": const fiveYearStart = currentDate; const fiveYearEnd = addDays(currentDate, 1825); // 5 years in days return { title: `${format(fiveYearStart, "yyyy")}-${format( fiveYearEnd, "yyyy" )}`, dates: eachYearOfInterval({ start: fiveYearStart, end: fiveYearEnd }), }; default: return { title: "", dates: [] }; } }, [selectedView]); const generateTimelineHeaders = (): string[] => { const { dates } = getTimelineData(); return dates.map((date) => format(date, "d MMM yyyy")); }; const isProjectVisible = (project: Project): boolean => { return ( (selectedGate === "all" || project.gate === selectedGate) && (selectedBuilding === "all" || project.building === selectedBuilding) ); }; const calculateBarPosition = (startDate: Date, endDate: Date) => { const timelineStart = new Date(2025, 0, 22); const timelineEnd = new Date(2030, 11, 31); const totalDays = (timelineEnd.getTime() - timelineStart.getTime()) / (1000 * 60 * 60 * 24); const startDays = (startDate.getTime() - timelineStart.getTime()) / (1000 * 60 * 60 * 24); const duration = (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24); const left = (startDays / totalDays) * 100; const width = (duration / totalDays) * 100; return { left: `${left}%`, width: `${width}%` }; }; const exportChart = useCallback(async () => { if (chartRef.current) { try { const canvas = await html2canvas(chartRef.current); const image = canvas.toDataURL("image/png"); const link = document.createElement("a"); link.href = image; link.download = "project-timeline.png"; link.click(); } catch (error) { console.error("Error exporting chart:", error); } } }, []); return ( <div className="w-full h-full bg-gray-100 p-8"> <div className="bg-white w-full rounded-lg shadow-lg p-6 mx-auto"> <div className="flex justify-between items-center mb-8"> <h1 className="text-3xl font-semibold text-gray-900"> REScout Open Sheet </h1> <div className="flex gap-4 items-center"> <select className="px-4 py-2 border rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500" value={selectedGate} onChange={(e) => setSelectedGate(e.target.value as Gate | "all")} > <option value="all">All Gates</option> {gates.map((gate) => ( <option key={gate} value={gate}> Gate {gate} </option> ))} </select> <select className="px-4 py-2 border rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500" value={selectedBuilding} onChange={(e) => setSelectedBuilding(e.target.value)} > <option value="all">All Buildings</option> {[...new Set(projects.map((p) => p.building))].map((building) => ( <option key={building} value={building}> {building} </option> ))} </select> <select className="px-4 py-2 border rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500" value={selectedView} onChange={(e) => setSelectedView(e.target.value as any)} > <option value="weekly">Weekly View</option> <option value="monthly">Monthly View</option> <option value="quarterly">Quarterly View</option> <option value="yearly">Yearly View</option> <option value="fiveYear">5-Year View</option> </select> <div className="flex gap-2"> <button onClick={() => setZoomLevel((prev) => Math.max(0.5, prev - 0.1)) } className="p-2 rounded-full hover:bg-gray-200" > <BiZoomOut size={24} /> </button> <button onClick={() => setZoomLevel((prev) => Math.min(2, prev + 0.1))} className="p-2 rounded-full hover:bg-gray-200" > <BiZoomIn size={24} /> </button> <button onClick={exportChart} className="p-2 rounded-full hover:bg-gray-200" > <MdFileDownload size={24} /> </button> </div> </div> </div> <div className="overflow-x-auto"> <div ref={chartRef} className="min-w-full" style={{ transform: `scale(${zoomLevel})`, transformOrigin: "top left", }} > <div className="grid grid-cols-[200px_1fr] gap-6"> <div className="sticky left-0 bg-white z-10"> <div className="h-12"></div> {gates.map((gate, index) => ( <div key={gate} className={`h-20 flex items-center px-4 py-2 ${ index % 2 === 0 ? "bg-gray-50" : "bg-white" } rounded-lg shadow-sm`} > <div className="flex items-center gap-3"> <div className={`w-6 h-6 rounded-full ${gateColors[gate]}`} ></div> <span className="font-medium text-lg">Gate {gate}</span> </div> </div> ))} </div> <div className="relative"> <div className="flex border-b sticky top-0 bg-white z-10"> {generateTimelineHeaders().map((header, index) => ( <div key={index} className="flex-1 p-4 text-sm font-medium text-center border-r" > {header} </div> ))} </div> <div className="relative"> {gates.map((gate, gateIndex) => ( <div key={gate} className={`h-20 relative ${ gateIndex % 2 === 0 ? "bg-gray-50" : "bg-white" }`} > {projects .filter( (project) => project.gate === gate && isProjectVisible(project) ) .map((project) => { const position = calculateBarPosition( project.startDate, project.endDate ); return ( <div key={project.id} className={`absolute h-10 top-6 rounded-lg ${ gateColors[project.gate as Gate] } transition-all duration-300 hover:h-12 hover:-top-1 group cursor-pointer`} style={position} > <div className="px-4 py-2 text-white text-sm whitespace-nowrap overflow-hidden"> {project.building} </div> <div className="hidden group-hover:block absolute top-full left-0 mt-2 bg-white p-3 rounded-lg shadow-lg z-20 w-64"> <h3 className="font-bold mb-2"> {project.building} </h3> <p>Gate: {project.gate}</p> <p> Start:{" "} {format(project.startDate, "dd MMM yyyy")} </p> <p> End: {format(project.endDate, "dd MMM yyyy")} </p> </div> </div> ); })} </div> ))} </div> </div> </div> </div> </div> </div> </div> ); }; export default ProjectTimeline; inside this code impliment it