feat(kanban): deadlines en cards (context menu, badges, calendario, history)
- migration 009 + columna deadline TEXT en cards - backend: CardPatch.HasDeadline, eventos deadline_set/deadline_cleared - KanbanCard: menu derecho con DatePicker, badge countdown con colores por ratio (azul>=50%, amarillo<50%, rojo<10%, red.9 overdue) - App.tsx: filtro "Con deadline", handleSetCardDeadline optimista, jump-to-card + highlight - CalendarView: popover por dia con seq_num + titulo, click navega a card en tablero - HistoryModal: render eventos deadline_set/deadline_cleared - .gitignore: *.log Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
Grid,
|
||||
Group,
|
||||
Loader,
|
||||
MultiSelect,
|
||||
Paper,
|
||||
Select,
|
||||
SimpleGrid,
|
||||
@@ -89,10 +90,16 @@ export function Dashboard({ users }: Props) {
|
||||
const [to, setTo] = useState<Date | null>(() => new Date());
|
||||
const [assigneeId, setAssigneeId] = useState<string | null>(null);
|
||||
const [requester, setRequester] = useState<string | null>(null);
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
const [tagOptions, setTagOptions] = useState<string[]>([]);
|
||||
const [data, setData] = useState<Metrics | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [requesterOptions, setRequesterOptions] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
api.listTags().then(setTagOptions).catch(() => {});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setLoading(true);
|
||||
@@ -102,6 +109,7 @@ export function Dashboard({ users }: Props) {
|
||||
to: fmtDate(to),
|
||||
assignee_id: assigneeId || undefined,
|
||||
requester: requester || undefined,
|
||||
tags: tags.length > 0 ? tags : undefined,
|
||||
})
|
||||
.then((m) => {
|
||||
if (cancelled) return;
|
||||
@@ -119,7 +127,7 @@ export function Dashboard({ users }: Props) {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [from, to, assigneeId, requester]);
|
||||
}, [from, to, assigneeId, requester, tags]);
|
||||
|
||||
const userOptions = useMemo(
|
||||
() => users.map((u) => ({ value: u.id, label: u.display_name || u.username })),
|
||||
@@ -240,6 +248,17 @@ export function Dashboard({ users }: Props) {
|
||||
searchable
|
||||
style={{ minWidth: 160 }}
|
||||
/>
|
||||
<MultiSelect
|
||||
label="Tags"
|
||||
size="xs"
|
||||
placeholder="Todas"
|
||||
value={tags}
|
||||
onChange={setTags}
|
||||
data={tagOptions}
|
||||
clearable
|
||||
searchable
|
||||
style={{ minWidth: 200 }}
|
||||
/>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user