feat(19): restyle tablo cards to production design with X/Y task count
- TabloCardView gains DoneTasks/TotalTasks int fields; handler stores raw counts alongside Progress % - TabloProjectCard: rounded-2xl card, border-[#EAECF0], purple-50 status badge, colored avatar initial (12x12 rounded-xl), calendar date row, Progression label + X/Y task count + purple-500 progress bar - List row: matching pill badge + X/Y count - All 7 TestTablosDashboard_* tests pass Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2a835f52e9
commit
7388418aed
4 changed files with 54 additions and 41 deletions
|
|
@ -63,14 +63,18 @@ func TablosListHandler(deps TablosDeps) http.HandlerFunc {
|
|||
// Non-fatal: proceed with Progress = 0 for all cards.
|
||||
progressRows = nil
|
||||
}
|
||||
progressMap := make(map[uuid.UUID]int, len(progressRows))
|
||||
type progressEntry struct{ done, total int }
|
||||
progressMap := make(map[uuid.UUID]progressEntry, len(progressRows))
|
||||
for _, p := range progressRows {
|
||||
if p.TotalTasks > 0 {
|
||||
progressMap[p.TabloID] = int(p.DoneTasks * 100 / p.TotalTasks)
|
||||
}
|
||||
progressMap[p.TabloID] = progressEntry{done: int(p.DoneTasks), total: int(p.TotalTasks)}
|
||||
}
|
||||
for i := range cardViews {
|
||||
cardViews[i].Progress = progressMap[cardViews[i].Tablo.ID]
|
||||
e := progressMap[cardViews[i].Tablo.ID]
|
||||
cardViews[i].DoneTasks = e.done
|
||||
cardViews[i].TotalTasks = e.total
|
||||
if e.total > 0 {
|
||||
cardViews[i].Progress = e.done * 100 / e.total
|
||||
}
|
||||
}
|
||||
|
||||
sidebarTablos := make([]sqlc.Tablo, 0, len(cardViews))
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -37,6 +37,8 @@ type TabloCardView struct {
|
|||
Tablo sqlc.Tablo
|
||||
DiscussionUnreadCount int64
|
||||
Progress int // 0–100; 0 when no tasks (D-05)
|
||||
DoneTasks int // raw count of done tasks
|
||||
TotalTasks int // raw count of all tasks
|
||||
}
|
||||
|
||||
func DiscussionPostURL(tabloID uuid.UUID) string {
|
||||
|
|
|
|||
|
|
@ -93,16 +93,16 @@ templ TablosEmptyState() {
|
|||
templ TabloProjectCard(card TabloCardView, csrfToken string) {
|
||||
<article id={ "tablo-" + card.Tablo.ID.String() } class="tablo-card-wrapper">
|
||||
<!-- Card view (default: visible in grid layout) -->
|
||||
<div class="project-card">
|
||||
<div class="project-card-top">
|
||||
<!-- Status badge top-left -->
|
||||
<div class="bg-white rounded-2xl p-4 border border-[#EAECF0] hover:shadow-md transition-shadow cursor-pointer project-card">
|
||||
<!-- Top row: status badge + delete button -->
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
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="px-3 py-1 rounded-full text-xs font-medium border bg-purple-50 text-purple-600 border-purple-200">
|
||||
{ strings.ToUpper(card.Tablo.Status[:1]) + card.Tablo.Status[1:] }
|
||||
</span>
|
||||
} else {
|
||||
<span></span>
|
||||
}
|
||||
<!-- Delete button top-right -->
|
||||
<div class="tablo-delete-zone">
|
||||
@ui.IconButton(ui.IconButtonProps{
|
||||
Label: "Delete tablo",
|
||||
|
|
@ -118,46 +118,53 @@ templ TabloProjectCard(card TabloCardView, csrfToken string) {
|
|||
})
|
||||
</div>
|
||||
</div>
|
||||
<div class="project-card-title-row">
|
||||
<!-- Avatar + title -->
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
if card.Tablo.Color.Valid && card.Tablo.Color.String != "" {
|
||||
<span class="project-avatar" style={ "background-color: " + card.Tablo.Color.String }>
|
||||
if len(card.Tablo.Title) > 0 {
|
||||
{ string([]rune(card.Tablo.Title)[0:1]) }
|
||||
}
|
||||
</span>
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center shrink-0 overflow-hidden" style={ "background-color: " + card.Tablo.Color.String }>
|
||||
<span class="text-white font-bold text-lg">
|
||||
if len(card.Tablo.Title) > 0 {
|
||||
{ string([]rune(card.Tablo.Title)[0:1]) }
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
} else {
|
||||
<span class="project-avatar">
|
||||
if len(card.Tablo.Title) > 0 {
|
||||
{ string([]rune(card.Tablo.Title)[0:1]) }
|
||||
}
|
||||
</span>
|
||||
<div class="w-12 h-12 rounded-xl flex items-center justify-center shrink-0 overflow-hidden bg-purple-500">
|
||||
<span class="text-white font-bold text-lg">
|
||||
if len(card.Tablo.Title) > 0 {
|
||||
{ string([]rune(card.Tablo.Title)[0:1]) }
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
<div class="tablo-title-zone">
|
||||
<h4>{ card.Tablo.Title }</h4>
|
||||
<h3 class="font-semibold text-gray-900 flex-1 truncate">{ card.Tablo.Title }</h3>
|
||||
</div>
|
||||
<!-- Date row -->
|
||||
<div class="flex items-center gap-2 text-sm text-gray-500 mb-4">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4" aria-hidden="true"><path d="M8 2v4"></path><path d="M16 2v4"></path><rect width="18" height="18" x="3" y="4" rx="2"></rect><path d="M3 10h18"></path></svg>
|
||||
<span>{ card.Tablo.CreatedAt.Time.Format("Jan 2, 2006") }</span>
|
||||
</div>
|
||||
<!-- Progress row -->
|
||||
<div class="mb-3 project-card-progress-row">
|
||||
<div class="flex items-center justify-between text-sm mb-2">
|
||||
<span class="text-gray-600">Progression:</span>
|
||||
<span class="font-semibold text-gray-900">{ strconv.Itoa(card.DoneTasks) }/{ strconv.Itoa(card.TotalTasks) }</span>
|
||||
</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 class="w-full bg-gray-100 rounded-full h-2 project-progress-track">
|
||||
<div class="h-2 rounded-full bg-purple-500 project-card-progress-bar" style={ "width: " + strconv.Itoa(card.Progress) + "%" }></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- List row (hidden by default, shown when data-view="list") -->
|
||||
<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="px-2 py-0.5 rounded-full text-xs font-medium border bg-purple-50 text-purple-600 border-purple-200">
|
||||
{ strings.ToUpper(card.Tablo.Status[:1]) + card.Tablo.Status[1:] }
|
||||
</span>
|
||||
}
|
||||
<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>
|
||||
<span class="tablo-list-row-meta">{ strconv.Itoa(card.DoneTasks) }/{ strconv.Itoa(card.TotalTasks) }</span>
|
||||
<div class="tablo-delete-zone">
|
||||
@ui.IconButton(ui.IconButtonProps{
|
||||
Label: "Delete tablo",
|
||||
|
|
|
|||
Loading…
Reference in a new issue