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:
+11
-18
@@ -8,27 +8,14 @@ import type {
|
||||
Sticker,
|
||||
User,
|
||||
} from "./types";
|
||||
import { fetchJSON as registryFetchJSON, HTTPError } from "@fn_library/infra/fetch_json";
|
||||
|
||||
export { HTTPError };
|
||||
|
||||
const BASE = "/api";
|
||||
|
||||
export class HTTPError extends Error {
|
||||
constructor(public status: number, message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchJSON<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
credentials: "include",
|
||||
...init,
|
||||
headers: { "Content-Type": "application/json", ...(init?.headers || {}) },
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ Message: res.statusText }));
|
||||
throw new HTTPError(res.status, err.Message || err.message || res.statusText);
|
||||
}
|
||||
if (res.status === 204) return undefined as T;
|
||||
return res.json();
|
||||
function fetchJSON<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
return registryFetchJSON<T>(path, init, BASE);
|
||||
}
|
||||
|
||||
export function getBoard(): Promise<Board> {
|
||||
@@ -84,6 +71,7 @@ export interface UpdateCardInput {
|
||||
locked?: boolean;
|
||||
assignee_id?: string | null;
|
||||
tags?: string[];
|
||||
deadline?: string | null;
|
||||
}
|
||||
|
||||
export function updateCard(id: string, patch: UpdateCardInput): Promise<void> {
|
||||
@@ -168,6 +156,10 @@ export function getMe(): Promise<User> {
|
||||
return fetchJSON("/me");
|
||||
}
|
||||
|
||||
export function updateMe(patch: { color?: string }): Promise<User> {
|
||||
return fetchJSON("/me", { method: "PATCH", body: JSON.stringify(patch) });
|
||||
}
|
||||
|
||||
export function listUsers(): Promise<User[]> {
|
||||
return fetchJSON("/users");
|
||||
}
|
||||
@@ -186,6 +178,7 @@ export function getMetrics(f: MetricsFilter): Promise<Metrics> {
|
||||
if (f.to) qs.set("to", f.to);
|
||||
if (f.assignee_id) qs.set("assignee_id", f.assignee_id);
|
||||
if (f.requester) qs.set("requester", f.requester);
|
||||
if (f.tags && f.tags.length > 0) qs.set("tags", f.tags.join(","));
|
||||
const q = qs.toString();
|
||||
return fetchJSON(`/metrics${q ? `?${q}` : ""}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user