+65
-99
internal/web/components/entity_tables.templ
+65
-99
internal/web/components/entity_tables.templ
···
5
5
"fmt"
6
6
)
7
7
8
-
// BeansTableProps defines props for the shared beans table
9
-
type BeansTableProps struct {
8
+
// BeanCardsProps defines props for the bean cards grid
9
+
type BeanCardsProps struct {
10
10
Beans []*models.Bean
11
11
ShowActions bool // Whether to show Edit/Delete actions
12
-
ShowStatus bool // Whether to show Status column (open/closed badge)
13
12
OwnerHandle string // If set, name links to view page with this owner
14
13
}
15
14
16
-
// BeansTable renders a table of beans with configurable columns and actions
17
-
templ BeansTable(props BeansTableProps) {
15
+
// BeanCards renders a grid of bean cards
16
+
templ BeanCards(props BeanCardsProps) {
18
17
if len(props.Beans) == 0 {
19
18
@EmptyState(EmptyStateProps{Message: "No beans yet."})
20
19
} else {
21
-
<div class="table-container overflow-x-auto">
22
-
<table class="table">
23
-
<thead class="table-header">
24
-
<tr>
25
-
if props.ShowStatus {
26
-
<th class="table-th whitespace-nowrap">๐ Status</th>
27
-
}
28
-
<th class="table-th whitespace-nowrap">๐ท๏ธ Name</th>
29
-
<th class="table-th whitespace-nowrap">๐ Origin</th>
30
-
<th class="table-th whitespace-nowrap">๐ฟ Variety</th>
31
-
<th class="table-th whitespace-nowrap">โ Roaster</th>
32
-
<th class="table-th whitespace-nowrap">๐ฅ Roast Level</th>
33
-
<th class="table-th whitespace-nowrap">๐ฑ Process</th>
34
-
if props.ShowActions {
35
-
<th class="table-th whitespace-nowrap">๐ Description</th>
36
-
<th class="table-th whitespace-nowrap">โ๏ธ Actions</th>
20
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
21
+
for _, bean := range props.Beans {
22
+
@BeanCard(bean, props.ShowActions, props.OwnerHandle)
23
+
}
24
+
</div>
25
+
}
26
+
}
27
+
28
+
// BeanCard renders a single bean as a compact card
29
+
templ BeanCard(bean *models.Bean, showActions bool, ownerHandle string) {
30
+
<div class="feed-card">
31
+
<div class="feed-content-box-sm">
32
+
<div class="flex items-start justify-between gap-2 mb-2">
33
+
<div class="min-w-0">
34
+
<div class="font-bold text-brown-900 text-base truncate">
35
+
if ownerHandle != "" {
36
+
<a href={ templ.SafeURL(fmt.Sprintf("/beans/%s?owner=%s", bean.RKey, ownerHandle)) } class="hover:underline">{ bean.Name }</a>
37
+
} else {
38
+
{ bean.Name }
37
39
}
38
-
</tr>
39
-
</thead>
40
-
<tbody class="table-body">
41
-
for _, bean := range props.Beans {
42
-
<tr class="table-row">
43
-
if props.ShowStatus {
44
-
<td class="px-6 py-4 text-sm text-brown-900">
45
-
if bean.Closed {
46
-
<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-brown-200 text-brown-800">Closed</span>
47
-
} else {
48
-
<span class="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-green-100 text-green-800">Open</span>
49
-
}
50
-
</td>
51
-
}
52
-
<td class="px-6 py-4 text-sm font-medium text-brown-900">
53
-
if props.OwnerHandle != "" {
54
-
<a href={ templ.SafeURL(fmt.Sprintf("/beans/%s?owner=%s", bean.RKey, props.OwnerHandle)) } class="hover:underline">{ bean.Name }</a>
55
-
} else {
56
-
{ bean.Name }
57
-
}
58
-
</td>
59
-
<td class="px-6 py-4 text-sm text-brown-900">{ bean.Origin }</td>
60
-
<td class="px-6 py-4 text-sm text-brown-900">
61
-
if bean.Variety != "" {
62
-
{ bean.Variety }
63
-
} else {
64
-
<span class="text-brown-400">-</span>
65
-
}
66
-
</td>
67
-
<td class="px-6 py-4 text-sm text-brown-900">
68
-
if bean.Roaster != nil && bean.Roaster.Name != "" {
69
-
{ bean.Roaster.Name }
70
-
} else {
71
-
<span class="text-brown-400">-</span>
72
-
}
73
-
</td>
74
-
<td class="px-6 py-4 text-sm text-brown-900">
75
-
if bean.RoastLevel != "" {
76
-
{ bean.RoastLevel }
77
-
} else {
78
-
<span class="text-brown-400">-</span>
79
-
}
80
-
</td>
81
-
<td class="px-6 py-4 text-sm text-brown-900">
82
-
if bean.Process != "" {
83
-
{ bean.Process }
84
-
} else {
85
-
<span class="text-brown-400">-</span>
86
-
}
87
-
</td>
88
-
if props.ShowActions {
89
-
<td class="px-6 py-4 text-sm text-brown-700">
90
-
if bean.Description != "" {
91
-
{ bean.Description }
92
-
} else {
93
-
<span class="text-brown-400">-</span>
94
-
}
95
-
</td>
96
-
<td class="px-6 py-4 text-sm font-medium space-x-2">
97
-
<button
98
-
hx-get={ "/api/modals/bean/" + bean.RKey }
99
-
hx-target="#modal-container"
100
-
hx-swap="innerHTML"
101
-
class="text-brown-700 hover:text-brown-900 font-medium"
102
-
>Edit</button>
103
-
<button
104
-
hx-delete={ "/api/beans/" + bean.RKey }
105
-
hx-confirm="Are you sure you want to delete this bean?"
106
-
hx-target="closest tr"
107
-
hx-swap="outerHTML swap:0.5s"
108
-
class="text-brown-600 hover:text-brown-800 font-medium"
109
-
>Delete</button>
110
-
</td>
111
-
}
112
-
</tr>
40
+
</div>
41
+
if bean.Roaster != nil && bean.Roaster.Name != "" {
42
+
<div class="text-sm text-brown-700 mt-0.5">
43
+
<span class="font-medium">๐ญ { bean.Roaster.Name }</span>
44
+
</div>
113
45
}
114
-
</tbody>
115
-
</table>
46
+
</div>
47
+
if showActions {
48
+
<div class="flex items-center gap-1 flex-shrink-0">
49
+
<button
50
+
hx-get={ "/api/modals/bean/" + bean.RKey }
51
+
hx-target="#modal-container"
52
+
hx-swap="innerHTML"
53
+
class="text-brown-600 hover:text-brown-900 text-sm font-medium px-1.5 py-0.5 rounded hover:bg-brown-200"
54
+
>Edit</button>
55
+
<button
56
+
hx-delete={ "/api/beans/" + bean.RKey }
57
+
hx-confirm="Are you sure you want to delete this bean?"
58
+
hx-target="closest .feed-card"
59
+
hx-swap="outerHTML swap:0.3s"
60
+
class="text-brown-500 hover:text-brown-800 text-sm font-medium px-1.5 py-0.5 rounded hover:bg-brown-200"
61
+
>Delete</button>
62
+
</div>
63
+
}
64
+
</div>
65
+
<div class="text-xs text-brown-600 mt-1 flex flex-wrap gap-x-2 gap-y-0.5">
66
+
if bean.Origin != "" {
67
+
<span class="inline-flex items-center gap-0.5">๐ { bean.Origin }</span>
68
+
}
69
+
if bean.Variety != "" {
70
+
<span class="inline-flex items-center gap-0.5">๐ฟ { bean.Variety }</span>
71
+
}
72
+
if bean.RoastLevel != "" {
73
+
<span class="inline-flex items-center gap-0.5">๐ฅ { bean.RoastLevel }</span>
74
+
}
75
+
if bean.Process != "" {
76
+
<span class="inline-flex items-center gap-0.5">๐ฑ { bean.Process }</span>
77
+
}
78
+
</div>
79
+
if bean.Description != "" {
80
+
<div class="mt-2 text-sm text-brown-800 italic line-clamp-2">"{ bean.Description }"</div>
81
+
}
116
82
</div>
117
-
}
83
+
</div>
118
84
}
119
85
120
86
// RoastersTableProps defines props for the shared roasters table
+17
-6
internal/web/components/manage_partial.templ
+17
-6
internal/web/components/manage_partial.templ
···
18
18
@ManageBrewersTab(props.Brewers)
19
19
}
20
20
21
-
// ManageBeansTab renders the beans tab content
21
+
// ManageBeansTab renders the beans tab content with open/closed sections
22
22
templ ManageBeansTab(beans []*models.Bean) {
23
23
<div x-show="tab === 'beans'">
24
24
<div class="mb-4 flex justify-between items-center">
···
32
32
+ Add Bean
33
33
</button>
34
34
</div>
35
-
@BeansTable(BeansTableProps{
36
-
Beans: beans,
37
-
ShowActions: true,
38
-
ShowStatus: true,
39
-
})
35
+
<div class="space-y-6">
36
+
<div>
37
+
<h4 class="text-lg font-semibold text-brown-900 mb-3">Open Bags</h4>
38
+
@BeanCards(BeanCardsProps{
39
+
Beans: filterOpenBeans(beans),
40
+
ShowActions: true,
41
+
})
42
+
</div>
43
+
<div>
44
+
<h4 class="text-lg font-semibold text-brown-900 mb-3">Closed Bags</h4>
45
+
@BeanCards(BeanCardsProps{
46
+
Beans: filterClosedBeans(beans),
47
+
ShowActions: true,
48
+
})
49
+
</div>
50
+
</div>
40
51
</div>
41
52
}
42
53
+2
-4
internal/web/components/profile_partial.templ
+2
-4
internal/web/components/profile_partial.templ
···
72
72
})
73
73
}
74
74
} else {
75
-
@BeansTable(BeansTableProps{
75
+
@BeanCards(BeanCardsProps{
76
76
Beans: filterOpenBeans(beans),
77
77
ShowActions: false,
78
-
ShowStatus: false,
79
78
OwnerHandle: profileHandle,
80
79
})
81
80
}
···
105
104
Message: "No closed bags.",
106
105
})
107
106
} else {
108
-
@BeansTable(BeansTableProps{
107
+
@BeanCards(BeanCardsProps{
109
108
Beans: filterClosedBeans(beans),
110
109
ShowActions: false,
111
-
ShowStatus: false,
112
110
OwnerHandle: profileHandle,
113
111
})
114
112
}
+3
internal/web/pages/feed.templ
+3
internal/web/pages/feed.templ
···
436
436
if item.Bean.Origin != "" {
437
437
<span class="inline-flex items-center gap-0.5">๐ { item.Bean.Origin }</span>
438
438
}
439
+
if item.Bean.Variety != "" {
440
+
<span class="inline-flex items-center gap-0.5">๐ฟ { item.Bean.Variety }</span>
441
+
}
439
442
if item.Bean.RoastLevel != "" {
440
443
<span class="inline-flex items-center gap-0.5">๐ฅ { item.Bean.RoastLevel }</span>
441
444
}
History
1 round
0 comments
pdewey.com
submitted
#0
1 commit
expand
collapse
feat: switch to card-based representation of beans in manage and profile
expand 0 comments
pull request successfully merged