Add a year navigator to the seasons’ calendar

This commit is contained in:
jordi fita mas 2023-09-29 18:20:16 +02:00
parent 47ec317010
commit 6939670dfc
4 changed files with 133 additions and 70 deletions

View File

@ -9,6 +9,9 @@ import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/jackc/pgtype"
@ -56,10 +59,12 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
}
case "range":
switch r.Method {
case http.MethodGet:
serveSeasonCalendar(w, r, user, company, conn)
case http.MethodPut:
updateSeasonCalendar(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
}
default:
if !uuid.Valid(head) {
@ -91,7 +96,7 @@ func serveSeasonIndex(w http.ResponseWriter, r *http.Request, user *auth.User, c
if err != nil {
panic(err)
}
calendar, err := collectSeasonCalendar(r.Context(), company, conn, 2023)
calendar, err := collectSeasonCalendar(r.Context(), company, conn, getCalendarYear(r.URL.Query()))
if err != nil {
panic(err)
}
@ -103,6 +108,16 @@ func serveSeasonIndex(w http.ResponseWriter, r *http.Request, user *auth.User, c
page.MustRender(w, r, user, company)
}
func getCalendarYear(query url.Values) int {
yearStr := strings.TrimSpace(query.Get("year"))
if yearStr != "" {
if year, err := strconv.Atoi(yearStr); err == nil {
return year
}
}
return time.Now().Year()
}
func collectSeasonEntries(ctx context.Context, company *auth.Company, conn *database.Conn) ([]*seasonEntry, error) {
rows, err := conn.Query(ctx, `
select slug
@ -167,7 +182,9 @@ func collectSeasonCalendar(ctx context.Context, company *auth.Company, conn *dat
var month *seasonMonth
var week seasonWeek
calendar := &seasonCalendar{}
calendar := &seasonCalendar{
Year: year,
}
weekday := int(time.Monday)
for rows.Next() {
day := &seasonDay{}
@ -213,6 +230,7 @@ func collectSeasonCalendar(ctx context.Context, company *auth.Company, conn *dat
}
type seasonCalendar struct {
Year int
Months []*seasonMonth
Form *calendarForm
}
@ -327,6 +345,11 @@ func (f *seasonForm) MustRender(w http.ResponseWriter, r *http.Request, user *au
template.MustRenderAdmin(w, r, user, company, "season/form.gohtml", f)
}
func serveSeasonCalendar(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
f := newCalendarForm(r.Context(), company, conn)
f.MustRender(w, r, user, company, conn, getCalendarYear(r.URL.Query()))
}
func updateSeasonCalendar(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
f := newCalendarForm(r.Context(), company, conn)
if ok, err := form.Handle(f, w, r, user); err != nil {
@ -346,7 +369,7 @@ func updateSeasonCalendar(w http.ResponseWriter, r *http.Request, user *auth.Use
f.StartDate.Val = ""
f.EndDate.Val = ""
}
f.MustRender(w, r, user, company, conn)
f.MustRender(w, r, user, company, conn, getCalendarYear(r.Form))
}
type calendarForm struct {
@ -427,8 +450,8 @@ func (f *calendarForm) Valid(l *locale.Locale) bool {
return v.AllOK
}
func (f *calendarForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
calendar, err := collectSeasonCalendar(r.Context(), company, conn, 2023)
func (f *calendarForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, year int) {
calendar, err := collectSeasonCalendar(r.Context(), company, conn, year)
if err != nil {
panic(err)
}

View File

@ -78,6 +78,12 @@ func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, templ
"queryEscape": func(s string) string {
return url.QueryEscape(s)
},
"inc": func(i int) int {
return i + 1
},
"dec": func(i int) int {
return i - 1
},
})
templates = append(templates, "form.gohtml")
files := make([]string, len(templates))

View File

@ -228,14 +228,14 @@ body > a[href="#content"]:focus {
}
/* header */
header {
body > header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--camper--header--background-color);
}
header, body > nav a {
body > header, body > nav a {
padding: 0 3rem;
}
@ -497,7 +497,38 @@ textarea {
background-color: #ffeeaa;
}
.season-calendar {
/* calendar */
.season-calendar button {
display: flex;
gap: 1em;
border: none;
cursor: pointer;
}
.season-calendar form button:first-child, .season-calendar > header button {
min-width: 0;
}
.season-calendar > header {
display: flex;
gap: 2rem;
justify-content: center;
align-items: center;
}
.season-calendar > header button:first-of-type {
order: -1;
}
.season-calendar > header button:first-of-type::before {
content: "←";
}
.season-calendar > header button:last-of-type::before {
content: "→";
}
.season-calendar > div {
display: grid;
grid-template-columns: repeat(3, auto);
grid-auto-rows: 1fr;
@ -507,11 +538,11 @@ textarea {
}
@media (max-width: 48rem) {
.season-calendar {
.season-calendar > div {
display: flex;
flex-direction: column;
}
.season-calendar table {
width: 100%;
}
@ -553,22 +584,14 @@ textarea {
height: .8rem;
}
.season-calendar form button {
display: flex;
gap: 1em;
border: none;
cursor: pointer;
}
.season-calendar form button:first-child {
min-width: 0;
position: absolute;
top: 0;
right: 0;
background-color: transparent;
}
.season-calendar form button:hover, .season-calendar form button:first-child:hover {
.season-calendar form button:hover, .season-calendar form button:first-child:hover {
background-color: var(--camper--color--hay);
}

View File

@ -1,58 +1,69 @@
<div class="season-calendar" data-hx-target="this" data-hx-swap="outerHTML">
<article class="season-calendar" data-hx-target="this" data-hx-swap="outerHTML">
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/season.seasonCalendar*/ -}}
{{ range .Months -}}
<table class="month">
<caption>{{ pgettext .Name "month" }}</caption>
<thead>
<tr>
<th scope="col">{{(pgettext "Mon" "day" )}}</th>
<th scope="col">{{(pgettext "Tue" "day" )}}</th>
<th scope="col">{{(pgettext "Wed" "day" )}}</th>
<th scope="col">{{(pgettext "Thu" "day" )}}</th>
<th scope="col">{{(pgettext "Fri" "day" )}}</th>
<th scope="col">{{(pgettext "Sat" "day" )}}</th>
<th scope="col">{{(pgettext "Sun" "day" )}}</th>
</tr>
</thead>
<tbody>
{{ range .Weeks }}
<header>
<h3>{{ .Year }}</h3>
<button type="button"
data-hx-get="/admin/seasons/range?year={{ dec .Year }}"><span class="sr-only">{{ pgettext "Prev" "navigation" }}</span></button>
<button type="button"
data-hx-get="/admin/seasons/range?year={{ inc .Year }}"><span class="sr-only">{{ pgettext "Next" "navigation" }}</span></button>
</header>
<div>
{{ range .Months -}}
<table class="month">
<caption>{{ pgettext .Name "month" }}</caption>
<thead>
<tr>
{{- range . }}
<td>
{{- if .Color -}}
<time style="background-color: {{ .Color }}"
datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "2" }}</time>
{{- end -}}
</td>
{{- end }}
<th scope="col">{{(pgettext "Mon" "day" )}}</th>
<th scope="col">{{(pgettext "Tue" "day" )}}</th>
<th scope="col">{{(pgettext "Wed" "day" )}}</th>
<th scope="col">{{(pgettext "Thu" "day" )}}</th>
<th scope="col">{{(pgettext "Fri" "day" )}}</th>
<th scope="col">{{(pgettext "Sat" "day" )}}</th>
<th scope="col">{{(pgettext "Sun" "day" )}}</th>
</tr>
{{- end }}
</tbody>
</table>
{{- end }}
{{ with .Form }}
<dialog>
<form data-hx-put="/admin/seasons/range">
{{ CSRFInput }}
{{ with .StartDate }}<input type="hidden" name="{{ .Name }}" value="{{ .Val }}">{{ end }}
{{ with .EndDate }}<input type="hidden" name="{{ .Name }}" value="{{ .Val }}">{{ end }}
<footer>
<button type="submit" name="season_id" value=""><span class="sr-only">{{( pgettext "Cancel" "action" )}}</span></button>
{{ range .Seasons -}}
<button type="submit" name="season_id" value="{{ .Slug }}">
<svg width="20px" height="20px">
<circle cx="50%" cy="50%" r="49%" fill="{{ .Color }}" stroke="#000" stroke-width=".5"/>
</svg>
{{ .Name }}
</button>
{{- end }}
</footer>
</form>
</dialog>
{{ end }}
</thead>
<tbody>
{{ range .Weeks }}
<tr>
{{- range . }}
<td>
{{- if .Color -}}
<time style="background-color: {{ .Color }}"
datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "2" }}</time>
{{- end -}}
</td>
{{- end }}
</tr>
{{- end }}
</tbody>
</table>
{{- end }}
{{ with .Form }}
<dialog>
<form data-hx-put="/admin/seasons/range">
{{ CSRFInput }}
<input type="hidden" name="year" value="{{ $.Year }}">
{{ with .StartDate }}<input type="hidden" name="{{ .Name }}" value="{{ .Val }}">{{ end }}
{{ with .EndDate }}<input type="hidden" name="{{ .Name }}" value="{{ .Val }}">{{ end }}
<footer>
<button type="submit"><span class="sr-only">{{( pgettext "Cancel" "action" )}}</span></button>
{{ range .Seasons -}}
<button type="submit" name="season_id" value="{{ .Slug }}">
<svg width="20px" height="20px">
<circle cx="50%" cy="50%" r="49%" fill="{{ .Color }}" stroke="#000"
stroke-width=".5"/>
</svg>
{{ .Name }}
</button>
{{- end }}
</footer>
</form>
</dialog>
{{ end }}
</div>
<script type="module">
import {setupCalendar} from "/static/camper.js";
setupCalendar(document.querySelector('.season-calendar'))
</script>
</div>
</article>