Canban

React JS
App
import React, { useState } from "react";
import Column from "./Column";
import "./styles.css";
function App() {
const initialColumns = {
backlog: {
title: "Backlog",
cards: [],
},
doing: {
title: "Doing",
cards: [],
},
qa: {
title: "QA",
cards: [],
},
production: {
title: "Production",
cards: [],
},
};
const [columns, setColumns] = useState(initialColumns);
const addCard = (columnId, title, description) => {
if (title && description) {
const newCard = {
id: Date.now().toString(),
title,
description,
};
setColumns((prevColumns) => ({
...prevColumns,
[columnId]: {
...prevColumns[columnId],
cards: [...prevColumns[columnId].cards, newCard],
},
}));
} else {
alert("Please enter both a title and a description for the card.");
}
};
const deleteCard = (columnId, cardId) => {
setColumns((prevColumns) => ({
...prevColumns,
[columnId]: {
...prevColumns[columnId],
cards: prevColumns[columnId].cards.filter((card) => card.id !== cardId),
},
}));
};
const moveCard = (sourceColumnId, targetColumnId, cardId) => {
const cardToMove = columns[sourceColumnId].cards.find(
(card) => card.id === cardId,
);
if (cardToMove) {
setColumns((prevColumns) => {
const sourceCards = prevColumns[sourceColumnId].cards.filter(
(card) => card.id !== cardId,
);
const targetCards = [...prevColumns[targetColumnId].cards, cardToMove];
return {
...prevColumns,
[sourceColumnId]: {
...prevColumns[sourceColumnId],
cards: sourceCards,
},
[targetColumnId]: {
...prevColumns[targetColumnId],
cards: targetCards,
},
};
});
}
};
return (
<div className="board">
{Object.entries(columns).map(([columnId, column]) => (
<Column
key={columnId}
id={columnId}
title={column.title}
cards={column.cards}
addCard={addCard}
deleteCard={deleteCard}
moveCard={moveCard}
/>
))}
</div>
);
}
export default App;
Column
import React from "react";
import Card from "./Card";
function Column({ id, title, cards, addCard, deleteCard, moveCard }) {
const handleDrop = (e) => {
e.preventDefault();
const cardId = e.dataTransfer.getData("cardId");
const sourceColumnId = e.dataTransfer.getData("sourceColumnId");
if (sourceColumnId !== id) {
moveCard(sourceColumnId, id, cardId);
}
};
const handleDragOver = (e) => {
e.preventDefault();
};
return (
<div
className="column"
id={id}
onDrop={handleDrop}
onDragOver={handleDragOver}
>
<h2>{title}</h2>
<div className="add-card">
<input type="text" placeholder="Card title" id={`${id}-title`} />
<textarea
placeholder="Card description"
id={`${id}-description`}
></textarea>
<button
onClick={() => {
const title = document.getElementById(`${id}-title`).value;
const description = document.getElementById(
`${id}-description`,
).value;
addCard(id, title, description);
document.getElementById(`${id}-title`).value = "";
document.getElementById(`${id}-description`).value = "";
}}
>
+
</button>
</div>
<div className="cards">
{cards.map((card) => (
<Card
key={card.id}
id={card.id}
title={card.title}
description={card.description}
deleteCard={() => deleteCard(id, card.id)}
/>
))}
</div>
</div>
);
}
export default Column;
Card
import React from "react";
function Card({ id, title, description, deleteCard }) {
const handleDragStart = (e) => {
e.dataTransfer.setData("cardId", id);
e.dataTransfer.setData("sourceColumnId", e.target.closest(".column").id);
};
return (
<div className="card" draggable onDragStart={handleDragStart}>
<h3>{title}</h3>
<p>{description}</p>
<button className="delete-btn" onClick={deleteCard}>
×
</button>
</div>
);
}
export default Card;
React TS
App
import React, { useState } from "react";
import Column from "./Column";
import "./styles.css";
interface Card {
id: string;
title: string;
description: string;
}
interface Columns {
[key: string]: {
title: string;
cards: Card[];
};
}
function App() {
const initialColumns: Columns = {
backlog: {
title: "Backlog",
cards: [],
},
doing: {
title: "Doing",
cards: [],
},
qa: {
title: "QA",
cards: [],
},
production: {
title: "Production",
cards: [],
},
};
const [columns, setColumns] = useState<Columns>(initialColumns);
const addCard = (columnId: string, title: string, description: string) => {
if (title && description) {
const newCard: Card = {
id: Date.now().toString(),
title,
description,
};
setColumns((prevColumns) => ({
...prevColumns,
[columnId]: {
...prevColumns[columnId],
cards: [...prevColumns[columnId].cards, newCard],
},
}));
} else {
alert("Please enter both a title and a description for the card.");
}
};
const deleteCard = (columnId: string, cardId: string) => {
setColumns((prevColumns) => ({
...prevColumns,
[columnId]: {
...prevColumns[columnId],
cards: prevColumns[columnId].cards.filter((card) => card.id !== cardId),
},
}));
};
const moveCard = (
sourceColumnId: string,
targetColumnId: string,
cardId: string,
) => {
const cardToMove = columns[sourceColumnId].cards.find(
(card) => card.id === cardId,
);
if (cardToMove) {
setColumns((prevColumns) => {
const sourceCards = prevColumns[sourceColumnId].cards.filter(
(card) => card.id !== cardId,
);
const targetCards = [...prevColumns[targetColumnId].cards, cardToMove];
return {
...prevColumns,
[sourceColumnId]: {
...prevColumns[sourceColumnId],
cards: sourceCards,
},
[targetColumnId]: {
...prevColumns[targetColumnId],
cards: targetCards,
},
};
});
}
};
return (
<div className="board">
{Object.entries(columns).map(([columnId, column]) => (
<Column
key={columnId}
id={columnId}
title={column.title}
cards={column.cards}
addCard={addCard}
deleteCard={deleteCard}
moveCard={moveCard}
/>
))}
</div>
);
}
export default App;
Column
import React, { useState } from "react";
import Card from "./Card";
interface ColumnProps {
id: string;
title: string;
cards: {
id: string;
title: string;
description: string;
}[];
addCard: (columnId: string, title: string, description: string) => void;
deleteCard: (columnId: string, cardId: string) => void;
moveCard: (
sourceColumnId: string,
targetColumnId: string,
cardId: string,
) => void;
}
const Column: React.FC<ColumnProps> = ({
id,
title,
cards,
addCard,
deleteCard,
moveCard,
}) => {
const [cardTitle, setCardTitle] = useState("");
const [cardDescription, setCardDescription] = useState("");
const handleAddCard = () => {
addCard(id, cardTitle, cardDescription);
setCardTitle("");
setCardDescription("");
};
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
const cardId = e.dataTransfer.getData("cardId");
const sourceColumnId = e.dataTransfer.getData("sourceColumnId");
if (sourceColumnId !== id) {
moveCard(sourceColumnId, id, cardId);
}
};
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
};
return (
<div
className="column"
id={id}
onDrop={handleDrop}
onDragOver={handleDragOver}
>
<h2>{title}</h2>
<div className="add-card">
<input
type="text"
placeholder="Card title"
value={cardTitle}
onChange={(e) => setCardTitle(e.target.value)}
/>
<textarea
placeholder="Card description"
value={cardDescription}
onChange={(e) => setCardDescription(e.target.value)}
></textarea>
<button onClick={handleAddCard}>+</button>
</div>
<div className="cards">
{cards.map((card) => (
<Card
key={card.id}
id={card.id}
title={card.title}
description={card.description}
deleteCard={() => deleteCard(id, card.id)}
/>
))}
</div>
</div>
);
};
export default Column;
Card
import React from "react";
interface CardProps {
id: string;
title: string;
description: string;
deleteCard: () => void;
}
const Card: React.FC<CardProps> = ({ id, title, description, deleteCard }) => {
const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
e.dataTransfer.setData("cardId", id);
e.dataTransfer.setData(
"sourceColumnId",
e.currentTarget.closest(".column")?.id || "",
);
};
return (
<div className="card" draggable onDragStart={handleDragStart}>
<h3>{title}</h3>
<p>{description}</p>
<button className="delete-btn" onClick={deleteCard}>
×
</button>
</div>
);
};
export default Card;