feat(19-02): rebuild TabloProjectCard with dual card+row structure
- Rewrote TabloProjectCard with outer article.tablo-card-wrapper (display:contents) - Added .project-card child: status badge top-left, delete button top-right - Avatar circle now shows initial letter from card.Tablo.Title - Added calendar icon + formatted date in .project-date-row - Added .project-card-progress-row with "Progression: X%" label and .project-card-progress-bar - Added .tablo-list-row sibling (hidden by default) for list view toggle - Added strconv and strings imports for Itoa and title-casing Status
This commit is contained in:
parent
ae1798062e
commit
4b254e9527
1 changed files with 72 additions and 29 deletions
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"backend/internal/auth"
|
"backend/internal/auth"
|
||||||
"backend/internal/db/sqlc"
|
"backend/internal/db/sqlc"
|
||||||
"backend/internal/web/ui"
|
"backend/internal/web/ui"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TablosDashboard renders the root authenticated dashboard with sidebar AppLayout.
|
// TablosDashboard renders the root authenticated dashboard with sidebar AppLayout.
|
||||||
|
|
@ -63,27 +65,27 @@ templ TablosEmptyState() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TabloProjectCard renders a single tablo as a project-card in the dashboard grid.
|
// TabloProjectCard renders a single tablo as a dual-element card+row wrapper.
|
||||||
// Follows D-C02 design: colored avatar circle, title zone (with inline-edit support),
|
// The outer article.tablo-card-wrapper contains both a .project-card (grid view)
|
||||||
// creation date, and edit/delete icon buttons.
|
// and a .tablo-list-row (list view, hidden by default).
|
||||||
|
// Matches production design: status badge top-left, delete button top-right,
|
||||||
|
// colored avatar with initial letter, title, date, and progress bar.
|
||||||
|
// Uses display:contents on the wrapper so it is transparent to grid/flex layout.
|
||||||
// Guards color rendering against null pgtype.Text values (Pitfall 6).
|
// Guards color rendering against null pgtype.Text values (Pitfall 6).
|
||||||
// Uses .Time accessor on pgtype.Timestamptz (Pitfall 6).
|
// Uses .Time accessor on pgtype.Timestamptz (Pitfall 6).
|
||||||
templ TabloProjectCard(card TabloCardView, csrfToken string) {
|
templ TabloProjectCard(card TabloCardView, csrfToken string) {
|
||||||
<article id={ "tablo-" + card.Tablo.ID.String() } class="project-card">
|
<article id={ "tablo-" + card.Tablo.ID.String() } class="tablo-card-wrapper">
|
||||||
<div class="project-card-top">
|
<!-- Card view (default: visible in grid layout) -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="project-card">
|
||||||
@ui.IconButton(ui.IconButtonProps{
|
<div class="project-card-top">
|
||||||
Label: "Edit title",
|
<!-- Status badge top-left -->
|
||||||
Icon: "pencil",
|
if len(card.Tablo.Status) > 0 {
|
||||||
Variant: ui.IconButtonVariantNeutral,
|
@ui.Badge(ui.BadgeProps{
|
||||||
Tone: ui.IconButtonToneGhost,
|
Label: strings.ToUpper(card.Tablo.Status[:1]) + card.Tablo.Status[1:],
|
||||||
Type: "button",
|
Variant: ui.BadgeVariantPrimary,
|
||||||
Attrs: templ.Attributes{
|
})
|
||||||
"hx-get": "/tablos/" + card.Tablo.ID.String() + "/edit-title",
|
}
|
||||||
"hx-target": "closest .tablo-title-zone",
|
<!-- Delete button top-right -->
|
||||||
"hx-swap": "outerHTML",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
<div class="tablo-delete-zone">
|
<div class="tablo-delete-zone">
|
||||||
@ui.IconButton(ui.IconButtonProps{
|
@ui.IconButton(ui.IconButtonProps{
|
||||||
Label: "Delete tablo",
|
Label: "Delete tablo",
|
||||||
|
|
@ -99,19 +101,60 @@ templ TabloProjectCard(card TabloCardView, csrfToken string) {
|
||||||
})
|
})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="project-card-title-row">
|
||||||
<div class="project-card-title-row">
|
if card.Tablo.Color.Valid && card.Tablo.Color.String != "" {
|
||||||
if card.Tablo.Color.Valid && card.Tablo.Color.String != "" {
|
<span class="project-avatar" style={ "background-color: " + card.Tablo.Color.String }>
|
||||||
<span class="project-avatar" style={ "background-color: " + card.Tablo.Color.String }></span>
|
if len(card.Tablo.Title) > 0 {
|
||||||
} else {
|
{ string([]rune(card.Tablo.Title)[0:1]) }
|
||||||
<span class="project-avatar"></span>
|
}
|
||||||
}
|
</span>
|
||||||
<div class="tablo-title-zone">
|
} else {
|
||||||
<h4>{ card.Tablo.Title }</h4>
|
<span class="project-avatar">
|
||||||
|
if len(card.Tablo.Title) > 0 {
|
||||||
|
{ string([]rune(card.Tablo.Title)[0:1]) }
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
<div class="tablo-title-zone">
|
||||||
|
<h4>{ card.Tablo.Title }</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="project-date-row">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="width:1rem;height:1rem;flex-shrink:0"><rect width="18" height="18" x="3" y="4" rx="2" ry="2"></rect><line x1="16" x2="16" y1="2" y2="6"></line><line x1="8" x2="8" y1="2" y2="6"></line><line x1="3" x2="21" y1="10" y2="10"></line></svg>
|
||||||
|
{ card.Tablo.CreatedAt.Time.Format("Jan 2, 2006") }
|
||||||
|
</div>
|
||||||
|
<div class="project-card-progress-row">
|
||||||
|
<span class="project-card-progress-label">Progression: { strconv.Itoa(card.Progress) }%</span>
|
||||||
|
<div class="project-progress-track">
|
||||||
|
<div class="project-card-progress-bar" style={ "width: " + strconv.Itoa(card.Progress) + "%" }></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="project-date-row">
|
<!-- List row (hidden by default, shown when data-view="list") -->
|
||||||
{ card.Tablo.CreatedAt.Time.Format("Jan 2, 2006") }
|
<div class="tablo-list-row">
|
||||||
|
if len(card.Tablo.Status) > 0 {
|
||||||
|
@ui.Badge(ui.BadgeProps{
|
||||||
|
Label: strings.ToUpper(card.Tablo.Status[:1]) + card.Tablo.Status[1:],
|
||||||
|
Variant: ui.BadgeVariantPrimary,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<span class="tablo-list-row-title">{ card.Tablo.Title }</span>
|
||||||
|
<span class="tablo-list-row-meta">{ card.Tablo.CreatedAt.Time.Format("Jan 2, 2006") }</span>
|
||||||
|
<span class="tablo-list-row-meta">{ strconv.Itoa(card.Progress) }%</span>
|
||||||
|
<div class="tablo-delete-zone">
|
||||||
|
@ui.IconButton(ui.IconButtonProps{
|
||||||
|
Label: "Delete tablo",
|
||||||
|
Icon: "trash",
|
||||||
|
Variant: ui.IconButtonVariantDanger,
|
||||||
|
Tone: ui.IconButtonToneGhost,
|
||||||
|
Type: "button",
|
||||||
|
Attrs: templ.Attributes{
|
||||||
|
"hx-get": "/tablos/" + card.Tablo.ID.String() + "/delete-confirm",
|
||||||
|
"hx-target": "closest .tablo-delete-zone",
|
||||||
|
"hx-swap": "outerHTML",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue