chore: auto-commit (10 archivos)

- chat.log
- db.go
- frontend/src/App.tsx
- frontend/src/api.ts
- frontend/src/components/CardForm.tsx
- frontend/src/components/Dashboard.tsx
- frontend/src/components/KanbanCard.tsx
- frontend/src/types.ts
- handlers.go
- metrics.go

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 15:55:35 +02:00
parent 9290a0b2d0
commit 2a727eb7c1
10 changed files with 583 additions and 52 deletions
+54 -6
View File
@@ -39,6 +39,8 @@ type Totals struct {
Cards int `json:"cards"`
CardsCompleted int `json:"cards_completed_in_range"`
CardsCreated int `json:"cards_created_in_range"`
CardsActive int `json:"cards_active"`
CardsDone int `json:"cards_done"`
Columns int `json:"columns"`
Users int `json:"users"`
ActiveLocks int `json:"active_locks"`
@@ -82,6 +84,8 @@ type AssigneeStat struct {
type RequesterStat struct {
Requester string `json:"requester"`
Total int `json:"total"`
Active int `json:"active"`
Completed int `json:"completed_in_range"`
}
type MovementStat struct {
@@ -137,12 +141,25 @@ func parseDateOrDefault(s string, dflt time.Time) time.Time {
return dflt
}
func parseEndDateOrDefault(s string, dflt time.Time) time.Time {
if s == "" {
return dflt
}
if t, err := time.Parse("2006-01-02", s); err == nil {
return t.Add(24*time.Hour - time.Nanosecond)
}
if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
return t
}
return dflt
}
// GET /api/metrics?from=YYYY-MM-DD&to=YYYY-MM-DD&assignee_id=...&requester=...
func handleMetrics(db *DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
now := time.Now().UTC()
from := parseDateOrDefault(r.URL.Query().Get("from"), now.AddDate(0, 0, -30))
to := parseDateOrDefault(r.URL.Query().Get("to"), now)
to := parseEndDateOrDefault(r.URL.Query().Get("to"), now)
assignee := r.URL.Query().Get("assignee_id")
requester := r.URL.Query().Get("requester")
@@ -160,7 +177,15 @@ func computeMetrics(db *DB, from, to time.Time, assignee, requester string) (*Me
toStr := to.Format(time.RFC3339Nano)
m := &Metrics{
Range: DateRange{From: from.Format("2006-01-02"), To: to.Format("2006-01-02")},
Range: DateRange{From: from.Format("2006-01-02"), To: to.Format("2006-01-02")},
ByColumn: []ColumnCount{},
ThroughputDaily: []DailyCount{},
CreatedDaily: []DailyCount{},
CycleTimeColumn: []ColumnDuration{},
TopAssignees: []AssigneeStat{},
TopRequesters: []RequesterStat{},
MovementsByUser: []MovementStat{},
CumulativeFlow: []CumulativePoint{},
}
cardWhere := "WHERE deleted_at IS NULL"
@@ -190,6 +215,18 @@ func computeMetrics(db *DB, from, to time.Time, assignee, requester string) (*Me
).Scan(&m.Totals.CardsCreated); err != nil {
return nil, err
}
if err := db.conn.QueryRow(
`SELECT COUNT(*) FROM cards `+cardWhere+` AND (completed_at IS NULL OR completed_at='')`,
args...,
).Scan(&m.Totals.CardsActive); err != nil {
return nil, err
}
if err := db.conn.QueryRow(
`SELECT COUNT(*) FROM cards `+cardWhere+` AND completed_at IS NOT NULL AND completed_at!=''`,
args...,
).Scan(&m.Totals.CardsDone); err != nil {
return nil, err
}
_ = completedArgs
if err := db.conn.QueryRow(`SELECT COUNT(*) FROM columns`).Scan(&m.Totals.Columns); err != nil {
@@ -276,12 +313,18 @@ func computeMetrics(db *DB, from, to time.Time, assignee, requester string) (*Me
}
colRows.Close()
now := time.Now().UTC()
cap := to
if now.Before(cap) {
cap = now
}
capStr := cap.Format(time.RFC3339Nano)
for _, ci := range cols {
histArgs := []any{ci.id, fromStr, toStr}
histQ := `SELECT (julianday(COALESCE(h.exited_at, ?)) - julianday(h.entered_at)) * 86400000
FROM card_column_history h JOIN cards c ON c.id=h.card_id
WHERE h.column_id=? AND h.entered_at>=? AND h.entered_at<=?`
histArgs = append([]any{toStr}, histArgs...)
histArgs = append([]any{capStr}, histArgs...)
if assignee != "" {
histQ += ` AND c.assignee_id=?`
histArgs = append(histArgs, assignee)
@@ -325,9 +368,14 @@ func computeMetrics(db *DB, from, to time.Time, assignee, requester string) (*Me
// Top requesters.
reqRows, err := db.conn.Query(
`SELECT requester, COUNT(*) as n FROM cards WHERE deleted_at IS NULL AND requester != '' AND created_at>=? AND created_at<=?`+
`SELECT requester,
COUNT(*) as total,
SUM(CASE WHEN completed_at IS NULL OR completed_at='' THEN 1 ELSE 0 END) as active,
SUM(CASE WHEN completed_at IS NOT NULL AND completed_at>=? AND completed_at<=? THEN 1 ELSE 0 END) as completed
FROM cards
WHERE deleted_at IS NULL AND requester != ''`+
condFromCard(assignee, "", "", "AND")+
` GROUP BY requester ORDER BY n DESC LIMIT 10`,
` GROUP BY requester ORDER BY total DESC LIMIT 10`,
topReqArgs(fromStr, toStr, assignee)...,
)
if err != nil {
@@ -335,7 +383,7 @@ func computeMetrics(db *DB, from, to time.Time, assignee, requester string) (*Me
}
for reqRows.Next() {
var s RequesterStat
if err := reqRows.Scan(&s.Requester, &s.Total); err != nil {
if err := reqRows.Scan(&s.Requester, &s.Total, &s.Active, &s.Completed); err != nil {
reqRows.Close()
return nil, err
}