import { useEffect, useState } from "preact/hooks"; import { cardDragType, readCardDrag } from "../drag"; import type { Card, ListWithCards } from "../types"; import { CardItem } from "./CardItem"; import { ConfirmDialog } from "./ConfirmDialog"; interface ListColumnProps { list: ListWithCards; managingLists: boolean; onEditList: (patch: { name?: string; position?: number }) => Promise; onDeleteList: () => Promise; onCreateCard: (title: string) => Promise; onEditCard: (cardID: number, patch: { title?: string; description?: string; done?: boolean }) => Promise; onMoveCard: (cardID: number, sourceListID: number, targetListID: number, index: number) => Promise; onOpenCard: (id: number) => void; } interface DropZoneProps { index: number; onDropCard: (cardID: number, sourceListID: number, index: number) => Promise; } function DropZone({ index, onDropCard }: DropZoneProps) { const [active, setActive] = useState(false); return (
{ if (event.dataTransfer?.types.includes(cardDragType)) { event.preventDefault(); event.dataTransfer.dropEffect = "move"; setActive(true); } }} onDragLeave={() => setActive(false)} onDrop={(event) => { event.preventDefault(); setActive(false); const payload = readCardDrag(event as unknown as DragEvent); if (payload) void onDropCard(payload.cardID, payload.sourceListID, index); }} /> ); } export function ListColumn({ list, onEditList, onDeleteList, onCreateCard, onEditCard, onMoveCard, onOpenCard, managingLists }: ListColumnProps) { const [editingName, setEditingName] = useState(false); const [name, setName] = useState(list.name); const [addingCard, setAddingCard] = useState(false); const [cardTitle, setCardTitle] = useState(""); const [confirming, setConfirming] = useState(false); const [saving, setSaving] = useState(false); useEffect(() => { if (!managingLists && editingName) { setName(list.name); setEditingName(false); } else if (!editingName) { setName(list.name); } }, [editingName, list.name, managingLists]); const saveName = async () => { if (!name.trim() || name.trim() === list.name) { setName(list.name); setEditingName(false); return; } setSaving(true); try { await onEditList({ name }); setEditingName(false); } catch { setName(list.name); } finally { setSaving(false); } }; const addCard = async () => { if (!cardTitle.trim()) return; setSaving(true); try { await onCreateCard(cardTitle); setCardTitle(""); setAddingCard(false); } catch { // App has shown an error toast. } finally { setSaving(false); } }; const remove = async () => { setConfirming(false); try { await onDeleteList(); } catch { // App has shown an error toast. } }; return ( <>
{editingName ? ( setName(event.currentTarget.value)} onKeyDown={(event) => { if (event.key === "Enter") void saveName(); if (event.key === "Escape") { setName(list.name); setEditingName(false); } }} onBlur={() => void saveName()} /> ) : managingLists ? ( ) : (

{list.name}

)} {list.cards.length}
{managingLists ? : null}
onMoveCard(cardID, sourceListID, list.id, index)} /> {list.cards.map((card: Card, index) => (
onEditCard(card.id, patch)} onOpen={() => onOpenCard(card.id)} /> onMoveCard(cardID, sourceListID, list.id, dropIndex)} />
))} {!list.cards.length ?

No cards yet. Drop one here or add the first task.

: null}
{addingCard ? (
{ event.preventDefault(); void addCard(); }} > setCardTitle(event.currentTarget.value)} onKeyDown={(event) => { if (event.key === "Escape") { setAddingCard(false); setCardTitle(""); } }} />
) : ( )}
setConfirming(false)} onConfirm={() => void remove()} /> ); }