# viewer
```jsx
const { useState, useQuery, useArray, useEffect } = dc;
const initialSettings = {
columns: {
"To Do": [],
"In Progress": [],
"Done": [],
"Unclean": [],
},
};
const initialPath = "COOKBOOK/RECIPES/ALL";
const KanbanBoard = ({ initialSettingsOverride = {} }) => {
const mergedSettings = {
...initialSettings,
...initialSettingsOverride,
};
const [columns, setColumns] = useState(mergedSettings.columns);
const [taskInput, setTaskInput] = useState("");
const [editingTask, setEditingTask] = useState(null);
const [queryPath, setQueryPath] = useState(initialPath);
const [nameFilter, setNameFilter] = useState("");
// Fetch recipes from the path
const query = `@page and path("${queryPath}")`;
const recipes = useQuery(query);
const filteredRecipes = useArray(recipes, (array) => {
if (!array) {
console.warn("No data returned from query.");
return [];
}
return array.where((x) => nameFilter === "" || x.$name.toLowerCase().includes(nameFilter.toLowerCase()));
});
useEffect(() => {
console.log("Fetched recipes (raw):", recipes);
console.log("Filtered recipes (processed):", filteredRecipes);
// Add fetched recipes to the "Unclean" column
if (filteredRecipes && filteredRecipes.length > 0) {
setColumns((prev) => ({
...prev,
Unclean: filteredRecipes.map((recipe) => ({ id: recipe.$name, title: recipe.$name })),
}));
} else {
console.warn("No recipes found for the given path or filter.");
}
}, [recipes, filteredRecipes]);
const addTask = () => {
if (!taskInput.trim()) return;
setColumns((prev) => ({
...prev,
"To Do": [...prev["To Do"], { id: Date.now(), title: taskInput.trim() }],
}));
setTaskInput("");
};
const removeTask = (columnKey, taskId) => {
setColumns((prev) => ({
...prev,
[columnKey]: prev[columnKey].filter((task) => task.id !== taskId),
}));
};
const moveTask = (task, fromColumn, toColumn) => {
setColumns((prev) => ({
...prev,
[fromColumn]: prev[fromColumn].filter((t) => t.id !== task.id),
[toColumn]: [...prev[toColumn], task],
}));
};
const handleEditTask = (columnKey, task) => {
setEditingTask({ columnKey, task });
setTaskInput(task.title);
};
const updateTask = () => {
if (!editingTask) return;
const { columnKey, task } = editingTask;
setColumns((prev) => ({
...prev,
[columnKey]: prev[columnKey].map((t) =>
t.id === task.id ? { ...t, title: taskInput.trim() } : t
),
}));
setEditingTask(null);
setTaskInput("");
};
return (
<div style={styles.kanbanBoard}>
<div style={styles.kanbanAddTask}>
<input
type="text"
value={taskInput}
onChange={(e) => setTaskInput(e.target.value)}
placeholder="Add a new task..."
style={styles.kanbanAddTaskInput}
/>
<button onClick={editingTask ? updateTask : addTask} style={styles.kanbanAddTaskButton}>
{editingTask ? "Update Task" : "Add Task"}
</button>
</div>
<div style={styles.kanbanColumns}>
{Object.keys(columns).map((columnKey) => (
<div key={columnKey} style={styles.kanbanColumn}>
<h2>{columnKey}</h2>
<div style={styles.kanbanTasks}>
{columns[columnKey].map((task) => (
<div key={task.id} style={styles.kanbanTask}>
<span>{task.title}</span>
<div style={styles.taskActions}>
<button onClick={() => handleEditTask(columnKey, task)} style={styles.taskActionButton}>
Edit
</button>
<button onClick={() => removeTask(columnKey, task.id)} style={styles.taskActionButton}>
Delete
</button>
{Object.keys(columns).map((targetColumnKey) => (
targetColumnKey !== columnKey && (
<button
key={targetColumnKey}
onClick={() => moveTask(task, columnKey, targetColumnKey)}
style={styles.taskActionButton}
>
Move to {targetColumnKey}
</button>
)
))}
</div>
</div>
))}
</div>
</div>
))}
</div>
</div>
);
};
const styles = {
kanbanBoard: {
display: 'flex',
flexDirection: 'column',
padding: '20px',
backgroundColor: '#f0f0f0',
minHeight: '100vh',
},
kanbanAddTask: {
display: 'flex',
marginBottom: '20px',
},
kanbanAddTaskInput: {
flex: 1,
padding: '10px',
fontSize: '16px',
},
kanbanAddTaskButton: {
padding: '10px',
marginLeft: '10px',
fontSize: '16px',
},
kanbanColumns: {
display: 'flex',
gap: '20px',
},
kanbanColumn: {
backgroundColor: '#ffffff',
borderRadius: '8px',
padding: '10px',
flex: 1,
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)',
},
kanbanTasks: {
display: 'flex',
flexDirection: 'column',
gap: '10px',
marginTop: '10px',
},
kanbanTask: {
backgroundColor: '#f9f9f9',
padding: '10px',
borderRadius: '4px',
boxShadow: '0 1px 5px rgba(0, 0, 0, 0.1)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
taskActions: {
display: 'flex',
},
taskActionButton: {
marginLeft: '5px',
},
};
return { KanbanBoard };
```