feat: add two-panel card detail modal with markdown, checklist, labels, dates, attachments
- CardDetailModal: two-panel layout (60/40) with inline title editing - MarkdownEditor: edit/preview toggle with react-markdown + remark-gfm - ChecklistSection: add/toggle/edit/delete items with progress counter - LabelPicker: toggle labels + create new labels with color swatches - DueDatePicker: date input with relative time and overdue styling - AttachmentSection: list with remove, placeholder add button - Wired into BoardView via selectedCardId state
This commit is contained in:
82
src/components/card-detail/DueDatePicker.tsx
Normal file
82
src/components/card-detail/DueDatePicker.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { format, formatDistanceToNow, isPast, isToday } from "date-fns";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useBoardStore } from "@/stores/board-store";
|
||||
|
||||
interface DueDatePickerProps {
|
||||
cardId: string;
|
||||
dueDate: string | null;
|
||||
}
|
||||
|
||||
export function DueDatePicker({ cardId, dueDate }: DueDatePickerProps) {
|
||||
const updateCard = useBoardStore((s) => s.updateCard);
|
||||
|
||||
const dateObj = dueDate ? new Date(dueDate) : null;
|
||||
const overdue = dateObj != null && isPast(dateObj) && !isToday(dateObj);
|
||||
|
||||
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const val = e.target.value;
|
||||
updateCard(cardId, { dueDate: val || null });
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
updateCard(cardId, { dueDate: null });
|
||||
}
|
||||
|
||||
// Format the date value for the HTML date input (YYYY-MM-DD)
|
||||
const inputValue = dateObj
|
||||
? format(dateObj, "yyyy-MM-dd")
|
||||
: "";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* Header */}
|
||||
<h4 className="font-mono text-xs uppercase tracking-widest text-pylon-text-secondary">
|
||||
Due Date
|
||||
</h4>
|
||||
|
||||
{/* Current date display */}
|
||||
{dateObj && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`text-sm font-medium ${
|
||||
overdue ? "text-pylon-danger" : "text-pylon-text"
|
||||
}`}
|
||||
>
|
||||
{format(dateObj, "MMM d, yyyy")}
|
||||
</span>
|
||||
<span
|
||||
className={`text-xs ${
|
||||
overdue ? "text-pylon-danger" : "text-pylon-text-secondary"
|
||||
}`}
|
||||
>
|
||||
{overdue
|
||||
? `overdue by ${formatDistanceToNow(dateObj)}`
|
||||
: isToday(dateObj)
|
||||
? "today"
|
||||
: `in ${formatDistanceToNow(dateObj)}`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Date input + clear */}
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="date"
|
||||
value={inputValue}
|
||||
onChange={handleChange}
|
||||
className="h-7 rounded-md border border-pylon-text-secondary/20 bg-pylon-column px-2 text-xs text-pylon-text outline-none focus:border-pylon-accent focus:ring-1 focus:ring-pylon-accent"
|
||||
/>
|
||||
{dueDate && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
onClick={handleClear}
|
||||
className="text-pylon-text-secondary hover:text-pylon-danger"
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user