- 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
83 lines
2.4 KiB
TypeScript
83 lines
2.4 KiB
TypeScript
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>
|
|
);
|
|
}
|