feat(ui): Jira-style card detail, edit-mode column management, design polish
All checks were successful
Build and deploy Havenllo / Verify Go and frontend (push) Successful in 32s
Build and deploy Havenllo / Build and push image (push) Successful in 33s
Build and deploy Havenllo / Apply and restart Havenllo (push) Successful in 7s

This commit is contained in:
Hermes
2026-07-14 10:37:10 -03:00
parent a082764e8a
commit 240f73b229
9 changed files with 769 additions and 389 deletions

View File

@@ -1,4 +1,4 @@
import { useState } from "preact/hooks";
import { useEffect, useState } from "preact/hooks";
import { cardDragType, readCardDrag } from "../drag";
import type { Card, ListWithCards } from "../types";
import { CardItem } from "./CardItem";
@@ -6,12 +6,13 @@ import { ConfirmDialog } from "./ConfirmDialog";
interface ListColumnProps {
list: ListWithCards;
managingLists: boolean;
onEditList: (patch: { name?: string; position?: number }) => Promise<void>;
onDeleteList: () => Promise<void>;
onCreateCard: (title: string) => Promise<void>;
onEditCard: (cardID: number, patch: { title?: string; description?: string; done?: boolean }) => Promise<void>;
onDeleteCard: (cardID: number) => Promise<void>;
onMoveCard: (cardID: number, sourceListID: number, targetListID: number, index: number) => Promise<void>;
onOpenCard: (id: number) => void;
}
interface DropZoneProps {
@@ -49,8 +50,9 @@ export function ListColumn({
onDeleteList,
onCreateCard,
onEditCard,
onDeleteCard,
onMoveCard
onMoveCard,
onOpenCard,
managingLists
}: ListColumnProps) {
const [editingName, setEditingName] = useState(false);
const [name, setName] = useState(list.name);
@@ -59,6 +61,15 @@ export function ListColumn({
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);
@@ -101,7 +112,7 @@ export function ListColumn({
return (
<>
<section class="list-column">
<section class={"list-column " + (managingLists ? "is-managing" : "")}>
<div class="list-header">
<div class="list-heading">
{editingName ? (
@@ -120,12 +131,14 @@ export function ListColumn({
}}
onBlur={() => void saveName()}
/>
) : (
) : managingLists ? (
<button type="button" class="list-name" onClick={() => setEditingName(true)}>{list.name}</button>
) : (
<h2 class="list-name-text">{list.name}</h2>
)}
<span class="count-badge">{list.cards.length}</span>
</div>
<button
{managingLists ? <button
type="button"
class="icon-button list-delete"
aria-label={"Delete " + list.name}
@@ -133,7 +146,7 @@ export function ListColumn({
onClick={() => setConfirming(true)}
>
×
</button>
</button> : null}
</div>
<div class="cards-stack">
<DropZone index={0} onDropCard={(cardID, sourceListID, index) => onMoveCard(cardID, sourceListID, list.id, index)} />
@@ -142,12 +155,12 @@ export function ListColumn({
<CardItem
card={card}
onEdit={(patch) => onEditCard(card.id, patch)}
onDelete={() => onDeleteCard(card.id)}
onOpen={() => onOpenCard(card.id)}
/>
<DropZone index={index + 1} onDropCard={(cardID, sourceListID, dropIndex) => onMoveCard(cardID, sourceListID, list.id, dropIndex)} />
</div>
))}
{!list.cards.length ? <p class="list-empty">Drop a card here or add the first task.</p> : null}
{!list.cards.length ? <p class="list-empty">No cards yet. Drop one here or add the first task.</p> : null}
</div>
{addingCard ? (
<form
@@ -193,4 +206,3 @@ export function ListColumn({
</>
);
}