added seasons list

This commit is contained in:
2026-02-01 13:25:11 +11:00
parent 96d534f045
commit 81d4ceb354
17 changed files with 660 additions and 679 deletions

View File

@@ -7,7 +7,12 @@ type NavItem struct {
// Return the list of navbar links
func getNavItems() []NavItem {
return []NavItem{}
return []NavItem{
{
name: "Seasons",
href: "/seasons",
},
}
}
// Returns the navbar template fragment

View File

@@ -0,0 +1,101 @@
package pagination
import "git.haelnorr.com/h/oslstats/internal/db"
import "fmt"
templ Pagination(opts db.PageOpts, total int) {
<div class="mt-6 flex flex-col gap-4">
<input type="hidden" name="page" id="pagination-page"/>
<input type="hidden" name="per_page" id="pagination-per-page"/>
<!-- Page info and per-page selector -->
<div class="flex flex-col sm:flex-row justify-between items-center gap-4 text-sm text-subtext0">
<div>
if total > 0 {
Showing { fmt.Sprintf("%d", opts.StartItem()) } - { fmt.Sprintf("%d", opts.EndItem(total)) } of { fmt.Sprintf("%d", total) } results
} else {
No results
}
</div>
<div class="flex items-center gap-2">
<label for="per-page-select">Per page:</label>
<select
id="per-page-select"
class="py-1 px-2 rounded-lg bg-surface0 border border-surface1 text-text focus:border-blue outline-none"
x-model.number="perPage"
@change="setPerPage(perPage)"
>
<option value="1">1</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</div>
<!-- Pagination buttons -->
if total > 0 && opts.TotalPages(total) > 1 {
<div class="flex flex-wrap justify-center items-center gap-2">
<!-- First button -->
<button
type="button"
@click="goToPage(1)"
class="px-3 py-2 rounded-lg border transition border-surface1 text-text bg-mantle"
x-bind:disabled={ fmt.Sprintf("%t", !opts.HasPrevPage()) }
x-bind:class={ fmt.Sprintf("%t", !opts.HasPrevPage()) +
" ? 'text-subtext0 cursor-not-allowed opacity-50' : 'hover:bg-surface0 hover:border-blue cursor-pointer'" }
>
<span class="hidden sm:inline">First</span>
<span class="sm:hidden">&lt;&lt;</span>
</button>
<!-- Previous button -->
<button
type="button"
@click={ fmt.Sprintf("goToPage(%d)", opts.Page-1) }
class="px-3 py-2 rounded-lg border transition border-surface1 text-text bg-mantle"
x-bind:disabled={ fmt.Sprintf("%t", !opts.HasPrevPage()) }
x-bind:class={ fmt.Sprintf("%t", !opts.HasPrevPage()) +
" ? 'text-subtext0 cursor-not-allowed opacity-50' : 'hover:bg-surface0 hover:border-blue cursor-pointer'" }
>
<span class="hidden sm:inline">Previous</span>
<span class="sm:hidden">&lt;</span>
</button>
<!-- Page numbers -->
for _, pageNum := range opts.GetPageRange(total, 7) {
<button
type="button"
@click={ fmt.Sprintf("goToPage(%d)", pageNum) }
class={ "px-3 py-2 rounded-lg border transition",
templ.KV("bg-blue border-blue text-mantle font-bold", pageNum == opts.Page),
templ.KV("bg-mantle border-surface1 text-text hover:bg-surface0 hover:border-blue cursor-pointer", pageNum != opts.Page) }
>
{ fmt.Sprintf("%d", pageNum) }
</button>
}
<!-- Next button -->
<button
type="button"
@click={ fmt.Sprintf("goToPage(%d)", opts.Page+1) }
class="px-3 py-2 rounded-lg border transition border-surface1 text-text bg-mantle"
x-bind:disabled={ fmt.Sprintf("%t", !opts.HasNextPage(total)) }
x-bind:class={ fmt.Sprintf("%t", !opts.HasNextPage(total)) +
" ? 'text-subtext0 cursor-not-allowed opacity-50' : 'hover:bg-surface0 hover:border-blue cursor-pointer'" }
>
<span class="hidden sm:inline">Next</span>
<span class="sm:hidden">&gt;</span>
</button>
<!-- Last button -->
<button
type="button"
@click={ fmt.Sprintf("goToPage(%d)", opts.TotalPages(total)) }
class="px-3 py-2 rounded-lg border transition border-surface1 text-text bg-mantle"
x-bind:disabled={ fmt.Sprintf("%t", !opts.HasNextPage(total)) }
x-bind:class={ fmt.Sprintf("%t", !opts.HasNextPage(total)) +
" ? 'text-subtext0 cursor-not-allowed opacity-50' : 'hover:bg-surface0 hover:border-blue cursor-pointer'" }
>
<span class="hidden sm:inline">Last</span>
<span class="sm:hidden">&gt;&gt;</span>
</button>
</div>
}
</div>
}

View File

@@ -0,0 +1,26 @@
package sort
import "git.haelnorr.com/h/oslstats/internal/db"
import "strings"
templ Dropdown(pageopts db.PageOpts, orderopts []db.OrderOpts) {
<div class="flex items-center gap-2">
<input type="hidden" name="order" id="sort-order"/>
<input type="hidden" name="order_by" id="sort-order-by"/>
<label for="sort-select" class="text-sm text-subtext0">Sort by:</label>
<select
id="sort-select"
class="py-2 px-3 rounded-lg bg-surface0 border border-surface1 text-text focus:border-blue outline-none"
@change="handleSortChange($event.target.value)"
>
for _, opt := range orderopts {
<option
value={ strings.Join([]string{opt.OrderBy, string(opt.Order)}, "|") }
selected?={ pageopts.OrderBy == opt.OrderBy && pageopts.Order == opt.Order }
>
{ opt.Label }
</option>
}
</select>
</div>
}

View File

@@ -5,7 +5,7 @@ import "git.haelnorr.com/h/oslstats/internal/view/layout"
// Page content for the index page
templ Index() {
@layout.Global("OSL Stats") {
<div class="text-center mt-24">
<div class="text-center mt-25">
<div class="text-4xl lg:text-6xl">OSL Stats</div>
<div>Placeholder text</div>
</div>

View File

@@ -0,0 +1,85 @@
package page
import "git.haelnorr.com/h/oslstats/internal/db"
import "git.haelnorr.com/h/oslstats/internal/view/layout"
import "git.haelnorr.com/h/oslstats/internal/view/component/pagination"
import "git.haelnorr.com/h/oslstats/internal/view/component/sort"
import "fmt"
import "github.com/uptrace/bun"
templ SeasonsPage(seasons *db.SeasonList) {
@layout.Global("Seasons") {
<div class="max-w-screen-2xl mx-auto px-2">
@SeasonsList(seasons)
</div>
}
}
templ SeasonsList(seasons *db.SeasonList) {
{{
sortOpts := []db.OrderOpts{
{
Order: bun.OrderAsc,
OrderBy: "name",
Label: "Name (A-Z)",
},
{
Order: bun.OrderDesc,
OrderBy: "name",
Label: "Name (Z-A)",
},
{
Order: bun.OrderAsc,
OrderBy: "short_name",
Label: "Short Name (A-Z)",
},
{
Order: bun.OrderDesc,
OrderBy: "short_name",
Label: "Short Name (Z-A)",
},
}
}}
<div id="seasons-list-container">
<form
id="seasons-form"
hx-target="#seasons-list-container"
hx-swap="outerHTML"
hx-push-url="true"
x-data={ templ.JSFuncCall("paginateData",
"seasons-form",
"/seasons",
seasons.PageOpts.Page,
seasons.PageOpts.PerPage,
seasons.PageOpts.Order,
seasons.PageOpts.OrderBy).CallInline }
>
<!-- Header with title and sort controls -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<h1 class="text-3xl font-bold">Seasons</h1>
@sort.Dropdown(seasons.PageOpts, sortOpts)
</div>
<!-- Results section -->
if len(seasons.Seasons) == 0 {
<div class="bg-mantle border border-surface1 rounded-lg p-8 text-center">
<p class="text-subtext0 text-lg">No seasons found</p>
</div>
} else {
<!-- Card grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
for _, season := range seasons.Seasons {
<a
class="bg-mantle border border-surface1 rounded-lg p-6 hover:bg-surface0 transition-colors"
href={ fmt.Sprintf("/seasons/%s", season.ShortName) }
>
<h3 class="text-xl font-bold text-text mb-2">{ season.Name }</h3>
</a>
}
</div>
<!-- Pagination controls -->
@pagination.Pagination(seasons.PageOpts, seasons.Total)
}
</form>
<script src="/static/js/pagination.js"></script>
</div>
}