package templates
import (
"backend/internal/db/sqlc"
"backend/internal/web/ui"
"github.com/google/uuid"
"strconv"
)
// groupTasksByStatus groups a flat task slice into a map keyed by TaskStatus.
// Returns a map; missing statuses return nil (empty) slices.
func groupTasksByStatus(tasks []sqlc.Task) map[sqlc.TaskStatus][]sqlc.Task {
result := make(map[sqlc.TaskStatus][]sqlc.Task, len(TaskColumns))
for _, t := range tasks {
result[t.Status] = append(result[t.Status], t)
}
return result
}
// EtapeGroup holds tasks for one etape within a kanban column.
type EtapeGroup struct {
EtapeID string // "" for unassigned
EtapeTitle string // "No etape" for unassigned
EtapeColor string // "" for unassigned
Tasks []sqlc.Task
}
// groupTasksByEtape groups tasks by etape in declaration order; unassigned last.
// Model: groupTasksByStatus above. Uses uuid.UUID(id.Bytes).String() same as taskEtapeIDString.
func groupTasksByEtape(tasks []sqlc.Task, etapes []sqlc.Etape) []EtapeGroup {
// Collect tasks per etape ID string
tasksByEtapeID := make(map[string][]sqlc.Task)
var unassigned []sqlc.Task
for _, t := range tasks {
if !t.EtapeID.Valid {
unassigned = append(unassigned, t)
} else {
keyStr := uuid.UUID(t.EtapeID.Bytes).String()
tasksByEtapeID[keyStr] = append(tasksByEtapeID[keyStr], t)
}
}
var groups []EtapeGroup
for _, etape := range etapes {
id := etape.ID.String()
ts := tasksByEtapeID[id]
if len(ts) == 0 {
continue
}
groups = append(groups, EtapeGroup{
EtapeID: id,
EtapeTitle: etape.Title,
EtapeColor: "", // Etape struct has no Color field in this schema
Tasks: ts,
})
}
if len(unassigned) > 0 {
groups = append(groups, EtapeGroup{
EtapeID: "",
EtapeTitle: "No etape",
EtapeColor: "",
Tasks: unassigned,
})
}
return groups
}
// EtapeGroupHeader renders the sub-heading row for an etape group within a kanban column.
// "No etape" / unassigned group omits the color dot and uses muted label style.
templ EtapeGroupHeader(group EtapeGroup) {
}
// KanbanBoard renders the outer board container with 4 columns and a hidden
// reorder form. Used by TabloDetailPage below the tablo header section.
// UI-SPEC §1 and D-08.
templ KanbanBoard(tabloID uuid.UUID, csrfToken string, tasks []sqlc.Task, filter EtapeFilter, etapes []sqlc.Etape) {
{{ grouped := groupTasksByStatus(tasks) }}
for _, status := range TaskColumns {
@KanbanColumn(tabloID, status, grouped[status], csrfToken, filter, etapes)
}
}
// KanbanColumn renders a single kanban column: header, task list, and add-task slot.
// Tasks are grouped by etape in declaration order; unassigned tasks appear last.
// UI-SPEC §1 and §2.
templ KanbanColumn(tabloID uuid.UUID, status sqlc.TaskStatus, tasks []sqlc.Task, csrfToken string, filter EtapeFilter, etapes []sqlc.Etape) {
} else {
{{ groups := groupTasksByEtape(tasks, etapes) }}
for _, group := range groups {
@EtapeGroupHeader(group)
for _, task := range group.Tasks {
@TaskCard(tabloID, task, csrfToken)
}
}
}
}
// TaskCard renders a single task card. The outer wrapper carries class "task-card-zone"
// and id="task-{task.ID}" so HTMX outerHTML swaps round-trip cleanly.
// UI-SPEC §4 and D-08.
templ TaskCard(tabloID uuid.UUID, task sqlc.Task, csrfToken string) {
}
// TaskCreateFormFragment renders the inline create form shown when a user clicks
// "+ Add task". Targets #column-{status} for HTMX beforeend swap on submit.
// UI-SPEC §2.
templ TaskCreateFormFragment(tabloID uuid.UUID, status sqlc.TaskStatus, form TaskCreateForm, errs TaskCreateErrors, csrfToken string, filter EtapeFilter, etapes []sqlc.Etape) {
}
// TaskDeleteConfirmFragment renders the delete confirmation dialog for a task.
// Carries class "task-card-zone" so outerHTML round-trips work correctly.
// UI-SPEC §5.
templ TaskDeleteConfirmFragment(tabloID uuid.UUID, task sqlc.Task, csrfToken string) {
}
// AddTaskTrigger renders the "+ Add task" button that expands to TaskCreateFormFragment.
// Targets #add-task-slot-{status} for innerHTML replacement.
// UI-SPEC §2.
templ AddTaskTrigger(tabloID uuid.UUID, status sqlc.TaskStatus, csrfToken string, filter EtapeFilter) {
}
// TaskCardGone renders an empty zone div with the task's id so HTMX outerHTML
// swap removes the card from the DOM after a successful delete (TASK-06).
// TODO: remove etapes and counts params after Phase 16 cleanup
templ TaskCardGone(taskID uuid.UUID, tabloID uuid.UUID, csrfToken string, filter EtapeFilter, etapes []sqlc.Etape, counts EtapeTaskCounts) {
}
// TaskCardOOB renders a new TaskCard AND an OOB swap that resets the add-task
// slot to AddTaskTrigger. Used by TaskCreateHandler to perform both operations
// in a single HTMX response.
// D-08/UI-SPEC §2: OOB swap resets #add-task-slot-{status} after create.
// TODO: remove etapes and counts params after Phase 16 cleanup
templ TaskCardOOB(status sqlc.TaskStatus, task sqlc.Task, tabloID uuid.UUID, csrfToken string, filter EtapeFilter, etapes []sqlc.Etape, counts EtapeTaskCounts) {
@TaskCard(tabloID, task, csrfToken)