+ if len(cards) == 0 {
+ @TablosEmptyState()
+ } else {
+ for _, card := range cards {
+ @TabloProjectCard(card, csrfToken)
+ }
}
- }
-
+
+
}
}
// TablosEmptyState renders the empty-state copy when a user has no tablos.
// Copy strings are locked by UI-SPEC Copywriting Contract.
+// Uses ui.EmptyState for consistent styling across the app (Phase 13).
templ TablosEmptyState() {
-
-
No tablos yet
-
Create your first tablo to get started.
-
- @ui.Button(ui.ButtonProps{
- Label: "New tablo",
- Variant: ui.ButtonVariantDefault,
- Tone: ui.ButtonToneSolid,
- Size: ui.SizeMD,
- Type: "button",
- Attrs: templ.Attributes{
- "hx-get": "/tablos/new",
- "hx-target": "#create-form-slot",
- "hx-swap": "innerHTML",
- "aria-label": "Create your first tablo",
- },
- })
+ @ui.EmptyState(ui.EmptyStateProps{
+ Title: "No tablos yet",
+ Description: "Create your first tablo to get started.",
+ Action: ui.Button(ui.ButtonProps{
+ Label: "New tablo",
+ Variant: ui.ButtonVariantDefault,
+ Tone: ui.ButtonToneSolid,
+ Size: ui.SizeMD,
+ Type: "button",
+ Attrs: templ.Attributes{
+ "hx-get": "/tablos/new",
+ "hx-target": "#create-form-slot",
+ "hx-swap": "innerHTML",
+ },
+ }),
+ })
+}
+
+// TabloProjectCard renders a single tablo as a project-card in the dashboard grid.
+// Follows D-C02 design: colored avatar circle, title zone (with inline-edit support),
+// creation date, and edit/delete icon buttons.
+// Guards color rendering against null pgtype.Text values (Pitfall 6).
+// Uses .Time accessor on pgtype.Timestamptz (Pitfall 6).
+templ TabloProjectCard(card TabloCardView, csrfToken string) {
+
+
+
}
// TabloCard renders a single tablo as a ui.Card on the dashboard.
@@ -179,23 +232,23 @@ templ TabloCardWithOOBFormClear(tablo sqlc.Tablo, csrfToken string) {
}
-// TabloDetailPage renders the full detail page for a single tablo with a tab layout.
-// Tabs: Overview / Tasks / Files / Events / Discussion. activeTab selects the initially rendered tab content.
+// TabloDetailPage renders the full detail page for a single tablo with a 3-tab layout.
+// Tabs: Overview / Tasks / Files. activeTab selects the initially rendered tab content.
// files and tasks are pre-fetched slices for the active tab (may be nil for inactive tabs).
+// activePath and sidebarTablos drive the AppLayout sidebar.
// UI-SPEC §3 Interaction Contract — GET /tablos/{id}.
// D-07: signature includes activeTab string param; D-08: tab bar links carry hx-push-url.
-templ TabloDetailPage(user *auth.User, csrfToken string, tablo sqlc.Tablo, tasks []sqlc.Task, etapes []sqlc.Etape, counts EtapeTaskCounts, filter EtapeFilter, files []sqlc.TabloFile, events EventsCalendar, discussion DiscussionTabData, activeTab string) {
- @Layout("Tablos — Xtablo", user, csrfToken) {
-
-
@@ -581,10 +624,10 @@ templ TabloDeleteConfirmFragment(tablo sqlc.Tablo, csrfToken string) {
// TabloNotFoundPage renders a 404 page for tablos that don't exist or are not
// accessible by the current user (D-04: 404 not 403 to avoid existence leakage).
-// user may be nil when called from an unauthenticated context — Layout handles nil.
+// activePath and sidebarTablos drive the AppLayout sidebar (pass "" and empty slice for not-found).
// UI-SPEC Copywriting Contract: "Not found" + "This tablo doesn't exist or you don't have access."
-templ TabloNotFoundPage(user *auth.User, csrfToken string) {
- @Layout("Not found", user, csrfToken) {
+templ TabloNotFoundPage(user *auth.User, csrfToken string, activePath string, sidebarTablos []sqlc.Tablo) {
+ @AppLayout("Not found", user, csrfToken, activePath, sidebarTablos) {
Not found
This tablo doesn't exist or you don't have access.