update booking list to card layout (#46)

### TL;DR
Transformed the bookings list from a table layout to a responsive card-based grid design.

### What changed?
- Replaced table structure with a responsive grid of cards
- Each booking is now displayed as a card with improved visual hierarchy
- Added hover effects and shadows for better interactivity
- Reorganized booking information with dedicated sections for dates and pricing
- Updated the search functionality to target the new card container
- Added euro symbol to price display
- Improved the presentation of canceled bookings

### How to test?
1. Navigate to the bookings list page
2. Verify cards display correctly on different screen sizes
3. Check that hover effects work on cards
4. Confirm search functionality still filters bookings
5. Verify canceled bookings show with strikethrough
6. Test that "View Details" links work correctly
7. Ensure all booking information is visible and properly formatted

### Why make this change?
The card-based layout provides a more modern and user-friendly interface that works better across different screen sizes. It improves the visual hierarchy of booking information and makes it easier for users to scan and interact with individual bookings. The new design also better accommodates varying content lengths and provides a more engaging visual experience.
This commit is contained in:
Ruidy 2025-03-02 13:44:43 +01:00 committed by GitHub
parent 9a6b6ef0c3
commit 17eb65ebdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 102 additions and 85 deletions

View file

@ -3,28 +3,45 @@ package view
import ()
templ BookingLines(bookings []*ListBookingsViewModel) {
<tbody>
<ul id="booking-cards" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4 list-none">
for _, b := range bookings {
<tr>
<th scope="row">
<a href={ b.Url }>
{ b.Id }
</a>
</th>
<td>
<span
<li class="card bg-base-200 shadow-xl hover:shadow-2xl transition-shadow duration-200">
<article class="card-body">
<div class="flex justify-between items-start">
<h2 class="card-title">
if b.Canceled {
class="line-through text-gray-500"
<span class="line-through text-gray-500">{ b.Name }</span>
} else {
<span>{ b.Name }</span>
}
>
{ b.Name }
</span>
</td>
<td>{ b.Total }</td>
<td>{ b.From }</td>
<td>{ b.To }</td>
<td>{ b.Platform }</td>
</tr>
</h2>
<div class="badge badge-primary">{ b.Platform }</div>
</div>
<div class="grid grid-cols-2 gap-2 my-4">
<div class="text-sm">
<div class="text-gray-500">Check-in</div>
<div class="font-medium">{ b.From }</div>
</div>
<div class="text-sm">
<div class="text-gray-500">Check-out</div>
<div class="font-medium">{ b.To }</div>
</div>
</div>
<div class="flex justify-between items-center mt-2">
<div class="text-lg font-bold text-primary">
{ b.Total } €
</div>
<div class="text-sm text-gray-500">
ID: { b.Id }
</div>
</div>
<div class="card-actions justify-end mt-4">
<a href={ b.Url } class="btn btn-primary btn-sm">
View Details
</a>
</div>
</article>
</li>
}
</tbody>
</ul>
}

View file

@ -31,114 +31,132 @@ func BookingLines(bookings []*ListBookingsViewModel) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<tbody>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<ul id=\"booking-cards\" class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4 list-none\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, b := range bookings {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<tr><th scope=\"row\"><a href=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<li class=\"card bg-base-200 shadow-xl hover:shadow-2xl transition-shadow duration-200\"><article class=\"card-body\"><div class=\"flex justify-between items-start\"><h2 class=\"card-title\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL = b.Url
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
if b.Canceled {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<span class=\"line-through text-gray-500\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">")
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(b.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 13, Col: 57}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(b.Id)
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(b.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 11, Col: 12}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 15, Col: 22}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</a></th><td><span")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if b.Canceled {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " class=\"line-through text-gray-500\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, ">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</h2><div class=\"badge badge-primary\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(b.Name)
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(b.Platform)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 20, Col: 14}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 18, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span></td><td>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div><div class=\"grid grid-cols-2 gap-2 my-4\"><div class=\"text-sm\"><div class=\"text-gray-500\">Check-in</div><div class=\"font-medium\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(b.Total)
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(b.From)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 23, Col: 17}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 23, Col: 40}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</td><td>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></div><div class=\"text-sm\"><div class=\"text-gray-500\">Check-out</div><div class=\"font-medium\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(b.From)
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(b.To)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 24, Col: 16}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 27, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</td><td>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div></div></div><div class=\"flex justify-between items-center mt-2\"><div class=\"text-lg font-bold text-primary\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(b.To)
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(b.Total)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 25, Col: 14}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 32, Col: 16}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</td><td>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " €</div><div class=\"text-sm text-gray-500\">ID: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(b.Platform)
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(b.Id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 26, Col: 20}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/booking_lines.templ`, Line: 35, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</td></tr>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></div><div class=\"card-actions justify-end mt-4\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 templ.SafeURL = b.Url
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var9)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" class=\"btn btn-primary btn-sm\">View Details</a></div></article></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</tbody>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View file

@ -14,26 +14,12 @@ templ ListBookings(bookings []*ListBookingsViewModel) {
name="search"
placeholder="Search bookings by name…"
hx-get="/bookings"
hx-target="tbody"
hx-target="#booking-cards"
hx-swap="outerHTML"
hx-trigger="keyup changed delay:500ms"
class="input input-bordered input-primary w-full md:w-96"
/>
</section>
<div class="overflow-x-auto">
<table class="table table-zebra w-full">
<thead>
<tr>
<th scope="col" class="text-left px-4 py-2 text-primary">ID</th>
<th scope="col" class="text-left px-4 py-2 text-primary">Name</th>
<th scope="col" class="text-left px-4 py-2 text-primary">Total (€)</th>
<th scope="col" class="text-left px-4 py-2 text-primary">From</th>
<th scope="col" class="text-left px-4 py-2 text-primary">To</th>
<th scope="col" class="text-left px-4 py-2 text-primary">Platform</th>
</tr>
</thead>
@BookingLines(bookings)
</table>
</div>
}
}

View file

@ -43,7 +43,7 @@ func ListBookings(bookings []*ListBookingsViewModel) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-4 p-4 w-[98%] mx-auto\"><hgroup class=\"flex-shrink-0\"><h1 class=\"text-3xl font-bold text-primary tracking-tight\">Bookings</h1><h2 class=\"text-muted text-lg font-normal\">Overview of the activity</h2></hgroup> <input type=\"search\" name=\"search\" placeholder=\"Search bookings by name…\" hx-get=\"/bookings\" hx-target=\"tbody\" hx-swap=\"outerHTML\" hx-trigger=\"keyup changed delay:500ms\" class=\"input input-bordered input-primary w-full md:w-96\"></section><div class=\"overflow-x-auto\"><table class=\"table table-zebra w-full\"><thead><tr><th scope=\"col\" class=\"text-left px-4 py-2 text-primary\">ID</th><th scope=\"col\" class=\"text-left px-4 py-2 text-primary\">Name</th><th scope=\"col\" class=\"text-left px-4 py-2 text-primary\">Total (€)</th><th scope=\"col\" class=\"text-left px-4 py-2 text-primary\">From</th><th scope=\"col\" class=\"text-left px-4 py-2 text-primary\">To</th><th scope=\"col\" class=\"text-left px-4 py-2 text-primary\">Platform</th></tr></thead>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"flex flex-col md:flex-row justify-between items-start md:items-center gap-4 mb-4 p-4 w-[98%] mx-auto\"><hgroup class=\"flex-shrink-0\"><h1 class=\"text-3xl font-bold text-primary tracking-tight\">Bookings</h1><h2 class=\"text-muted text-lg font-normal\">Overview of the activity</h2></hgroup> <input type=\"search\" name=\"search\" placeholder=\"Search bookings by name…\" hx-get=\"/bookings\" hx-target=\"#booking-cards\" hx-swap=\"outerHTML\" hx-trigger=\"keyup changed delay:500ms\" class=\"input input-bordered input-primary w-full md:w-96\"></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -51,10 +51,6 @@ func ListBookings(bookings []*ListBookingsViewModel) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</table></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = layout.BaseLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)