added seasons list
This commit is contained in:
@@ -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
|
||||
|
||||
101
internal/view/component/pagination/pagination.templ
Normal file
101
internal/view/component/pagination/pagination.templ
Normal 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"><<</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"><</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">></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">>></span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
26
internal/view/component/sort/dropdown.templ
Normal file
26
internal/view/component/sort/dropdown.templ
Normal 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>
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
85
internal/view/page/seasons_list.templ
Normal file
85
internal/view/page/seasons_list.templ
Normal 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>
|
||||
}
|
||||
Reference in New Issue
Block a user