Skip to content

Commit

Permalink
feat: poc drag n drop
Browse files Browse the repository at this point in the history
  • Loading branch information
alcpereira committed Mar 17, 2024
1 parent 6456aa6 commit 41fb21c
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 11 deletions.
43 changes: 40 additions & 3 deletions app/components/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,48 @@
type CardPropsTypes = {
import clsx from "clsx";
import { useState } from "react";

export type CardPropsTypes = {
title: string;
content: string;
id: number;
columnId: number;
};

export default function Card({ title, content }: CardPropsTypes) {
export default function Card({ title, content, id, columnId }: CardPropsTypes) {
const [isDragging, setIsDragging] = useState(false);

const handleOnDragStart = (e: React.DragEvent<HTMLDivElement>) => {
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.setData("application/hunters-card", id.toString());
e.dataTransfer.setData("application/hunters-column", columnId.toString());
setTimeout(() => {
setIsDragging(true); // This is a hack to make the "placeholder" card a different color
}, 0);
console.log("drag start for id: ", { id, columnId });
};

const handleOnDragEnd = () => {
setIsDragging(false);
};

const handleOnDragOver = (e: React.DragEvent<HTMLDivElement>) => {
// To use later for ordering within the column
e.preventDefault();
};

return (
<div draggable className="border-2 bg-sky-50 text-xl">
<div
className={clsx(
"border-2",
"bg-slate-500",
isDragging && "opacity-50", // Set styles for the place holder here
"text-xl",
)}
draggable
onDragStart={handleOnDragStart}
onDragEnd={handleOnDragEnd}
onDragOver={handleOnDragOver}
>
<h1>{title}</h1>
<p>{content}</p>
</div>
Expand Down
62 changes: 57 additions & 5 deletions app/components/Column/Column.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,65 @@
import Card from "~/components/Card/Card";
import clsx from "clsx";
import { useState } from "react";
import Card, { CardPropsTypes } from "~/components/Card/Card";
import { useSubmit } from "@remix-run/react";

type ColumnPropsTypes = {
cards: CardPropsTypes[];
columnId: number;
};

export default function Column({ cards, columnId }: ColumnPropsTypes) {
const [isDraggedOver, setIsDraggedOver] = useState(false);
const [hasDrop, setHasDrop] = useState(false);
const submit = useSubmit();

const handleOnDrop = (e: React.DragEvent<HTMLDivElement>) => {
// Needed to be a droppable zone
e.preventDefault(); // Should add filtering to check if valid drop
const cardId = e.dataTransfer.getData("application/hunters-card");
console.log("drop", { cardId, columnId });
submit({ cardId, columnId }, { method: "POST", navigate: false });
setIsDraggedOver(false);
setHasDrop(true);
setTimeout(() => {
setHasDrop(false);
}, 1000);
};

const handleOnDragOver = (e: React.DragEvent<HTMLDivElement>) => {
e.dataTransfer.setData("application/hunters-column", columnId.toString());

// Needed to be a droppable zone
e.preventDefault(); // Should add filtering to check if valid drop
setIsDraggedOver(true);
};

const handleOnDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
e.dataTransfer.clearData("application/hunters-column");
setIsDraggedOver(false);
};

const handleOnDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
e.dataTransfer.setData("application/hunters-column", columnId.toString());
console.log("drag enter");
};

export default function Column() {
return (
<div
className="border border-solid border-1 border-gray-700 rounded-md py-5 px-2"
onDragOver={() => console.log("dragging over")}
className={clsx(
"border-1 min-h-48 rounded-md border border-solid border-gray-700 px-2 py-5",
isDraggedOver ? "bg-sky-50" : "bg-slate-500",
[hasDrop && "bg-red-400"],
)}
onDrop={handleOnDrop}
onDragOver={handleOnDragOver}
onDragLeave={handleOnDragLeave}
onDragEnter={handleOnDragEnter}
>
<h1>This is a column</h1>
<Card title="hello" content="world" />
{cards.map((card) => (
<Card key={card.id} {...card} />
))}
</div>
);
}
41 changes: 41 additions & 0 deletions app/routes/board/queries.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import db from "~/db/db.server";

export async function getBoards() {
const { rows } = await db.execute("SELECT * FROM queries");
return rows;
}

export type ColumnType = { id: number; name: string };
export type CardType = { id: number; name: string; columnId: number };
export async function getCardsAndColumns() {
const { rows: column } = await db.execute({
sql: `
SELECT column.id, column.name FROM column
LEFT JOIN board ON column.boardId = board.id
WHERE board.id = ?;
`,
args: [1],
});

const { rows: card } = await db.execute({
sql: `
SELECT card.id, card.name, card.columnId FROM card
FULL JOIN column ON card.columnId = column.id
FULL JOIN board ON column.boardId = board.id
WHERE board.id = ?;
`,
args: [1],
});

return { column, card } as unknown as {
column: ColumnType[];
card: CardType[];
};
}

export async function mutateCardColumn(cardId: number, columnId: number) {
await db.execute({
sql: "UPDATE card SET columnId = ? WHERE id = ?;",
args: [columnId, cardId],
});
}
52 changes: 49 additions & 3 deletions app/routes/board/route.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,59 @@
import Column from "~/components/Column/Column";
import {
type CardType,
type ColumnType,
getCardsAndColumns,
mutateCardColumn,
} from "./queries.server";
import { useLoaderData } from "@remix-run/react";
import { ActionFunctionArgs } from "@remix-run/node";
import invariant from "invariant";

export async function loader() {
const data = await getCardsAndColumns();
return data;
}

export async function action({ request }: ActionFunctionArgs) {
const data = await request.formData();
const cardId = data.get("cardId");
invariant(cardId, "cardId is required");
const columnId = data.get("columnId");
invariant(columnId, "columnId is required");
await mutateCardColumn(
parseInt(cardId.toString()),
parseInt(columnId.toString()),
);

return { ok: true };
}

function mapCardsToColumns(columns: ColumnType[], cards: CardType[]) {
return columns.map((column) => {
return {
...column,
cards: cards.filter((card) => card.columnId === column.id),
};
});
}

export default function Board() {
const { column, card } = useLoaderData<typeof loader>();
const columns = mapCardsToColumns(column, card);
return (
<div>
<h1>Board</h1>
<p>{JSON.stringify(columns, null, 4)}</p>
<div className="flex flex-row gap-2">
<Column />
<Column />
<Column />
{columns.map((column) => {
const cards = column.cards.map((card) => ({
...card,
title: card.name,
content: card.name,
id: card.id,
}));
return <Column cards={cards} key={column.id} columnId={column.id} />;
})}
</div>
</div>
);
Expand Down

0 comments on commit 41fb21c

Please sign in to comment.