feat(kanban): reporte diario al click en dia del calendario (issue 0093)

Adds a daily report dashboard accessible by clicking a day number in the
calendar view. Renders inside a full-width modal (90% width).

Backend (new file backend/reports.go):
- Type DailyReport with KPIs, rankings, done_cards list, reopened cards,
  3-bucket stale list (7/14/30d), lead time avg+p50+p95, 24-hour
  movement histogram, deadlines met/missed list, tag distribution and
  archived count.
- DB.DailyReportFor(date, tz) uses Europe/Madrid by default; computes
  [start,end) in local time, converts to UTC and queries:
  * cards.completed_at in range  -> done list
  * card_events kind=created in range -> created counts
  * card_column_history.entered_at in range -> moves + hourly
  * previousColumnWasDone() -> reopened detection
  * card_lock_history overlapping the day -> blocked_ms
  * stale buckets: open history entries on non-done columns aged >=7d
- New route GET /api/reports/daily?date=YYYY-MM-DD&tz=Europe/Madrid.

Frontend:
- api.ts: DailyReport type + dailyReport(date, tz?) call.
- New component DailyReportView (components/DailyReport.tsx):
  * 6 KPI cards (Hechas, Creadas, Movimientos, Bloqueado, Reabiertas,
    Deadlines on-time %).
  * 4 ranking cards (Top assignees done, Top assignees created,
    Top requesters atendidas, Top requesters aportadas).
  * Done cards table with click-to-jump (links open the card in board).
  * Mantine BarChart with movements per hour.
  * Tag chips, reopened list, deadlines list with late_ms, stale buckets.
- CalendarView wraps the day number in UnstyledButton with data-test
  attribute and forwards onOpenDailyReport.
- App.handleOpenDailyReport opens modals.open size 90% with the view;
  click on a card title closes the modal and jumps to the board with
  highlight (reuses existing handleJumpToCard).

Tests (e2e/daily-report.spec.ts):
- Endpoint shape: kpis, done_cards, hourly_moves[24], stale buckets.
- Calendar day click opens the modal with "Reporte diario" title and
  KPI labels visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 17:43:29 +02:00
parent 9d3ab5f0f3
commit fc7e6a34a7
10 changed files with 2497 additions and 1187 deletions
+18 -1
View File
@@ -72,6 +72,7 @@ import { CardForm } from "./components/CardForm";
import { CardEditPanel } from "./components/CardEditPanel";
import { ChatPanel } from "./components/ChatPanel";
import { CalendarView } from "./components/CalendarView";
import { DailyReportView } from "./components/DailyReport";
import { Dashboard } from "./components/Dashboard";
import { HistoryModal } from "./components/HistoryModal";
import { KanbanCard } from "./components/KanbanCard";
@@ -720,6 +721,22 @@ export function App() {
window.setTimeout(() => setHighlightCardId(null), 3000);
}, []);
const handleOpenDailyReport = useCallback((date: string) => {
const id = modals.open({
title: "Reporte diario",
size: "90%",
children: (
<DailyReportView
date={date}
onJumpToCard={(cardId) => {
modals.close(id);
handleJumpToCard(cardId);
}}
/>
),
});
}, [handleJumpToCard]);
const handleSetCardDeadline = useCallback(async (id: string, deadline: string | null) => {
setBoard((prev) => {
if (!prev) return prev;
@@ -1324,7 +1341,7 @@ export function App() {
</Box>
) : activeTab === "calendar" ? (
<Box style={{ height: "calc(100vh - 50px)", overflow: "auto" }}>
<CalendarView users={users} cards={board.cards} onJumpToCard={handleJumpToCard} />
<CalendarView users={users} cards={board.cards} onJumpToCard={handleJumpToCard} onOpenDailyReport={handleOpenDailyReport} />
</Box>
) : (
<Box style={{ height: "calc(100vh - 50px)", overflow: "hidden", display: "flex", flexDirection: "column" }}>