Hi thanks for your reply, this is my page.
``import React, { useRef, useCallback, useState, useEffect } from 'react';
import { Gantt, Toolbar, Willow } from "wx-react-gantt";
import { RestDataProvider } from "wx-gantt-data-provider";
import "wx-react-gantt/dist/gantt.css";
import { supabase } from '../../../lib/supabase';
interface GanttChartProps {
projectId: string;
}
interface TaskFormData {
text: string;
start: Date;
end: Date;
duration: number;
progress: number;
type: string;
}
export function GanttChart({ projectId }: GanttChartProps) {
const apiRef = useRef(null);
const [selectedTask, setSelectedTask] = useState(null);
const [showForm, setShowForm] = useState(false);
const [tasks, setTasks] = useState([
{
id: 1,
text: "Project Planning",
start: new Date(2024, 5, 11),
end: new Date(2024, 6, 12),
duration: 1,
progress: 0,
type: "task",
}
]);
const [showSidebar, setShowSidebar] = useState(false);
const [formData, setFormData] = useState<TaskFormData>({
text: "New Task",
start: new Date(),
end: new Date(Date.now() + 24 * 60 * 60 * 1000),
duration: 1,
progress: 0,
type: "task"
});
useEffect(() => {
// Load tasks from Supabase when component mounts
const loadTasks = async () => {
try {
const { data, error } = await supabase
.from('gantt_tasks')
.select('*')
.eq('project_id', projectId)
.order('created_at', { ascending: true });
if (error) throw error;
if (data && data.length > 0) {
const formattedTasks = data.map(task => ({
id: task.id,
text: task.text,
start: new Date(task.start_date),
end: new Date(task.end_date),
duration: task.duration,
progress: task.progress,
type: task.type,
parent: task.parent_id
}));
setTasks(formattedTasks);
}
} catch (err) {
console.error('Error loading tasks:', err);
}
};
loadTasks();
}, [projectId]);
const createTask = async (taskData) => {
try {
const newTask = {
project_id: projectId,
text: taskData.text "New Task",
start_date: taskData.start.toISOString(),
end_date: taskData.end.toISOString(),
duration: taskData.duration 1,
progress: taskData.progress || 0,
type: "task",
parent_id: null
};
const { data, error } = await supabase
.from('gantt_tasks')
.insert([newTask])
.select()
.single();
if (error) throw error;
const formattedTask = {
id: data.id,
text: data.text,
start: new Date(data.start_date),
end: new Date(data.end_date),
duration: data.duration,
progress: data.progress,
type: "task",
parent: data.parent_id
};
setTasks(prev => [...prev, formattedTask]);
return formattedTask;
} catch (err) {
console.error('Error creating task:', err);
return null;
}
};
const handleInit = useCallback((api) => {
if (api) {
apiRef.current = api;
// Initialize the API with tasks and links
api.exec("init", {
tasks: tasks,
links: []
});
// Set up event handlers after API is initialized
api.on("task-click", (taskId) => {
const task = tasks.find(t => t.id === taskId);
if (task) {
setSelectedTask(task);
}
});
}
}, [tasks]);
const handleTaskUpdate = useCallback(async (task: any) => {
try {
// Format dates to ISO string for Supabase
const { error } = await supabase
.from('gantt_tasks')
.update({
text: task.text,
start_date: new Date(task.start).toISOString(),
end_date: new Date(task.end).toISOString(),
duration: task.duration,
progress: task.progress 0,
type: task.type 'task',
parent_id: task.parent || null
})
.eq('id', task.id);
if (error) throw error;
// Update local tasks with the original task object from the Gantt component
setTasks(prev => prev.map(t => t.id === task.id ? task : t));
return true;
} catch (err) {
console.error('Error updating task:', err);
return false;
}
}, []);
const handleTaskDelete = useCallback(async (taskId: string) => {
try {
const { error } = await supabase
.from('gantt_tasks')
.delete()
.eq('id', taskId);
if (error) throw error;
// Update local tasks
setTasks(prev => prev.filter(t => t.id !== taskId));
return true;
} catch (err) {
console.error('Error deleting task:', err);
return false;
}
}, []);
const handleLinkCreate = useCallback(async (link: any) => {
try {
const { data, error } = await supabase
.from('gantt_links')
.insert([{
source_task_id: link.source,
target_task_id: link.target,
type: link.type || 'finish_to_start'
}])
.select()
.single();
if (error) throw error;
return { id: data.id };
} catch (err) {
console.error('Error creating link:', err);
return null;
}
}, []);
const handleLinkDelete = useCallback(async (linkId: string) => {
try {
const { error } = await supabase
.from('gantt_links')
.delete()
.eq('id', linkId);
if (error) throw error;
return true;
} catch (err) {
console.error('Error deleting link:', err);
return false;
}
}, []);
const handleFormChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
if (name === 'start' name === 'end') {
setFormData(prev => ({
...prev,
[name]: new Date(value)
}));
} else if (name === 'progress' name === 'duration') {
setFormData(prev => ({
...prev,
[name]: Number(value)
}));
} else {
setFormData(prev => ({
...prev,
[name]: value
}));
}
};
const handleFormSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const newTask = await createTask(formData);
if (newTask && apiRef.current) {
apiRef.current.exec("refresh");
}
setShowSidebar(false);
};
const scales = [
{ unit: "month", step: 1, format: "MMMM yyy" },
{ unit: "day", step: 1, format: "d" },
];
const zoomConfig = {
maxCellWidth: 400,
level: 3,
levels: [
{
minCellWidth: 250,
scales: [
{ unit: "quarter", step: 1, format: "QQQQ" },
{ unit: "month", step: 1, format: "MMMM yyy" },
],
},
{
minCellWidth: 100,
scales: [
{ unit: "month", step: 1, format: "MMMM yyy" },
{ unit: "week", step: 1, format: "'week' w" },
],
},
{
maxCellWidth: 200,
scales: [
{ unit: "month", step: 1, format: "MMMM yyy" },
{ unit: "day", step: 1, format: "d" },
],
},
],
};
const toolbarItems = [
{
id: "add-task",
comp: "button",
icon: "wxi-plus",
text: "New task",
type: "primary",
handler: async () => {
const defaultTask = {
text: "New Task",
start: new Date(),
end: new Date(Date.now() + 24 * 60 * 60 * 1000),
duration: 1,
progress: 0,
type: "task"
};
const newTask = await createTask(defaultTask);
if (newTask && apiRef.current) {
apiRef.current.exec("refresh");
}
}
},
{
id: "edit-task",
comp: "button",
icon: "wxi-edit",
text: "Edit",
handler: () => {
if (selectedTask) {
handleTaskUpdate(selectedTask);
}
}
},
{
id: "delete-task",
comp: "button",
icon: "wxi-delete",
text: "Delete",
handler: () => {
if (selectedTask) {
handleTaskDelete(selectedTask.id);
setSelectedTask(null);
}
}
}
];
return (
<div className="w-full h-full relative">
<Willow>
<div className="flex items-center gap-4 mb-4">
<Toolbar items={toolbarItems} />
<button
onClick={() => setShowSidebar(true)}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
New Task (Form)
</button>
</div>
<Gantt
apiRef={apiRef}
tasks={tasks}
scales={scales}
zoom={zoomConfig}
onTaskUpdate={handleTaskUpdate}
onTaskDelete={handleTaskDelete}
onLinkCreate={handleLinkCreate}
onLinkDelete={handleLinkDelete}
onInit={handleInit}
/>
</Willow>
{/* Custom Sidebar Form */}
{showSidebar && (
<div className="fixed inset-y-0 right-0 w-96 bg-white dark:bg-gray-800 shadow-xl transform transition-transform duration-300 ease-in-out">
<div className="p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-lg font-medium text-gray-900 dark:text-white">New Task</h2>
<button
onClick={() => setShowSidebar(false)}
className="text-gray-400 hover:text-gray-500"
>
×
</button>
</div>
<form onSubmit={handleFormSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Task Name
</label>
<input
type="text"
name="text"
value={formData.text}
onChange={handleFormChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Start Date
</label>
<input
type="datetime-local"
name="start"
value={formData.start.toISOString().slice(0, 16)}
onChange={handleFormChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
End Date
</label>
<input
type="datetime-local"
name="end"
value={formData.end.toISOString().slice(0, 16)}
onChange={handleFormChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Duration (days)
</label>
<input
type="number"
name="duration"
value={formData.duration}
onChange={handleFormChange}
min="1"
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Progress (%)
</label>
<input
type="number"
name="progress"
value={formData.progress}
onChange={handleFormChange}
min="0"
max="100"
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm"
/>
</div>
<div className="pt-4">
<button
type="submit"
className="w-full inline-flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Create Task
</button>
</div>
</form>
</div>
</div>
)}
</div>
);
} ``