Add the calendar to the public campsite type page
I had to export the Calendar type from Season to use it from campsite/types, and also renamed them because season.SeasonCalendar is a bit redundant compared to just season.Calendar. I still have not added the HTMx code to switch year because i am not sure whether Oriol will want to show a whole year or just half a year. The calculation for the text color taking into account the contrast with the background is from [0]. [0]: https://www.smashingmagazine.com/2020/07/css-techniques-legibility/#foreground-contrast
This commit is contained in:
parent
e1575c6edd
commit
852acaccc3
|
@ -9,6 +9,7 @@ import (
|
|||
"context"
|
||||
gotemplate "html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
|
@ -17,6 +18,7 @@ import (
|
|||
"dev.tandem.ws/tandem/camper/pkg/database"
|
||||
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
||||
"dev.tandem.ws/tandem/camper/pkg/locale"
|
||||
"dev.tandem.ws/tandem/camper/pkg/season"
|
||||
"dev.tandem.ws/tandem/camper/pkg/template"
|
||||
"dev.tandem.ws/tandem/camper/pkg/uuid"
|
||||
)
|
||||
|
@ -35,7 +37,7 @@ func (h *PublicHandler) Handler(user *auth.User, company *auth.Company, conn *da
|
|||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
page, err := newPublicPage(r.Context(), conn, user.Locale, head)
|
||||
page, err := newPublicPage(r.Context(), company, conn, user.Locale, head)
|
||||
if database.ErrorIsNotFound(err) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
|
@ -55,6 +57,7 @@ type publicPage struct {
|
|||
Carousel []*carousel.Slide
|
||||
Prices []*typePrice
|
||||
Features []*typeFeature
|
||||
Calendar *season.Calendar
|
||||
Spiel gotemplate.HTML
|
||||
Info gotemplate.HTML
|
||||
Facilities gotemplate.HTML
|
||||
|
@ -74,7 +77,7 @@ type typeFeature struct {
|
|||
Name string
|
||||
}
|
||||
|
||||
func newPublicPage(ctx context.Context, conn *database.Conn, loc *locale.Locale, slug string) (*publicPage, error) {
|
||||
func newPublicPage(ctx context.Context, company *auth.Company, conn *database.Conn, loc *locale.Locale, slug string) (*publicPage, error) {
|
||||
prices, err := collectPrices(ctx, conn, loc.Language, slug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -83,9 +86,14 @@ func newPublicPage(ctx context.Context, conn *database.Conn, loc *locale.Locale,
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
calendar, err := season.CollectSeasonCalendar(ctx, company, conn, time.Now().Year())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
page := &publicPage{
|
||||
PublicPage: template.NewPublicPage(),
|
||||
Carousel: mustCollectSlides(ctx, conn, loc, slug),
|
||||
Calendar: calendar,
|
||||
Prices: prices,
|
||||
Features: features,
|
||||
}
|
||||
|
@ -129,7 +137,13 @@ func collectPrices(ctx context.Context, conn *database.Conn, language language.T
|
|||
group by season_id
|
||||
) as option on option.season_id = season.season_id
|
||||
where season.active
|
||||
`, language, slug)
|
||||
union all
|
||||
select $3
|
||||
, to_color($4)::text
|
||||
, 1
|
||||
, ''
|
||||
, false
|
||||
`, language, slug, locale.PgettextNoop("Closed", "season"), season.UnsetColor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -171,5 +185,5 @@ func collectFeatures(ctx context.Context, conn *database.Conn, language language
|
|||
|
||||
func (p *publicPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
|
||||
p.Setup(r, user, company, conn)
|
||||
template.MustRenderPublic(w, r, user, company, "campsite/type.gohtml", p)
|
||||
template.MustRenderPublicFiles(w, r, user, company, p, "campsite/type.gohtml", "campsite/calendar.gohtml")
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
"dev.tandem.ws/tandem/camper/pkg/uuid"
|
||||
)
|
||||
|
||||
const unsetColor = 0xd1d0df
|
||||
const UnsetColor = 0xd1d0df
|
||||
|
||||
type AdminHandler struct {
|
||||
locales locale.Locales
|
||||
|
@ -124,7 +124,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, getCalendarYear(r.URL.Query()))
|
||||
calendar, err := CollectSeasonCalendar(r.Context(), company, conn, getCalendarYear(r.URL.Query()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ var longMonthNames = []string{
|
|||
locale.PgettextNoop("December", "month"),
|
||||
}
|
||||
|
||||
func collectSeasonCalendar(ctx context.Context, company *auth.Company, conn *database.Conn, year int) (*seasonCalendar, error) {
|
||||
func CollectSeasonCalendar(ctx context.Context, company *auth.Company, conn *database.Conn, year int) (*Calendar, error) {
|
||||
firstDay := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
lastDay := time.Date(year, time.December, 31, 23, 59, 59, 0, time.UTC)
|
||||
rows, err := conn.Query(ctx, `
|
||||
|
@ -222,19 +222,19 @@ func collectSeasonCalendar(ctx context.Context, company *auth.Company, conn *dat
|
|||
from generate_series($2, $3, interval '1 day') as t(day)
|
||||
left join season_calendar on season_range @> t.day::date
|
||||
left join season on season.season_id = season_calendar.season_id and company_id = $4
|
||||
`, unsetColor, firstDay, lastDay, company.ID)
|
||||
`, UnsetColor, firstDay, lastDay, company.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var month *seasonMonth
|
||||
var week seasonWeek
|
||||
calendar := &seasonCalendar{
|
||||
var month *Month
|
||||
var week Week
|
||||
calendar := &Calendar{
|
||||
Year: year,
|
||||
}
|
||||
weekday := int(time.Monday)
|
||||
for rows.Next() {
|
||||
day := &seasonDay{}
|
||||
day := &Day{}
|
||||
if err = rows.Scan(&day.Date, &day.Color); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -242,32 +242,32 @@ func collectSeasonCalendar(ctx context.Context, company *auth.Company, conn *dat
|
|||
if month == nil || month.Month != dayMonth {
|
||||
if month != nil {
|
||||
for ; weekday != int(time.Sunday); weekday = (weekday + 1) % 7 {
|
||||
week = append(week, &seasonDay{})
|
||||
week = append(week, &Day{})
|
||||
}
|
||||
month.Weeks = append(month.Weeks, week)
|
||||
calendar.Months = append(calendar.Months, month)
|
||||
}
|
||||
month = &seasonMonth{
|
||||
month = &Month{
|
||||
Month: dayMonth,
|
||||
Name: longMonthNames[dayMonth-1],
|
||||
}
|
||||
week = seasonWeek{}
|
||||
week = Week{}
|
||||
weekday = int(time.Monday)
|
||||
dayWeekday := int(day.Date.Weekday())
|
||||
for ; weekday != dayWeekday; weekday = (weekday + 1) % 7 {
|
||||
week = append(week, &seasonDay{})
|
||||
week = append(week, &Day{})
|
||||
}
|
||||
}
|
||||
week = append(week, day)
|
||||
weekday = (weekday + 1) % 7
|
||||
if weekday == int(time.Monday) {
|
||||
month.Weeks = append(month.Weeks, week)
|
||||
week = seasonWeek{}
|
||||
week = Week{}
|
||||
}
|
||||
}
|
||||
if month != nil {
|
||||
for ; weekday != int(time.Sunday); weekday = (weekday + 1) % 7 {
|
||||
week = append(week, &seasonDay{})
|
||||
week = append(week, &Day{})
|
||||
}
|
||||
month.Weeks = append(month.Weeks, week)
|
||||
calendar.Months = append(calendar.Months, month)
|
||||
|
@ -276,28 +276,28 @@ func collectSeasonCalendar(ctx context.Context, company *auth.Company, conn *dat
|
|||
return calendar, nil
|
||||
}
|
||||
|
||||
type seasonCalendar struct {
|
||||
type Calendar struct {
|
||||
Year int
|
||||
Months []*seasonMonth
|
||||
Months []*Month
|
||||
Form *calendarForm
|
||||
}
|
||||
|
||||
type seasonMonth struct {
|
||||
type Month struct {
|
||||
Month time.Month
|
||||
Name string
|
||||
Weeks []seasonWeek
|
||||
Weeks []Week
|
||||
}
|
||||
|
||||
type seasonWeek []*seasonDay
|
||||
type Week []*Day
|
||||
|
||||
type seasonDay struct {
|
||||
type Day struct {
|
||||
Date time.Time
|
||||
Color string
|
||||
}
|
||||
|
||||
type seasonIndex struct {
|
||||
Seasons []*seasonEntry
|
||||
Calendar *seasonCalendar
|
||||
Calendar *Calendar
|
||||
}
|
||||
|
||||
func (page *seasonIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
||||
|
@ -457,7 +457,7 @@ func mustCollectCalendarSeasons(ctx context.Context, company *auth.Company, conn
|
|||
from season
|
||||
where company_id = $3
|
||||
and active
|
||||
order by sort, name`, locale.PgettextNoop("Unset", "action"), unsetColor, company.ID)
|
||||
order by sort, name`, locale.PgettextNoop("Unset", "action"), UnsetColor, company.ID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -498,7 +498,7 @@ func (f *calendarForm) Valid(l *locale.Locale) bool {
|
|||
}
|
||||
|
||||
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)
|
||||
calendar, err := CollectSeasonCalendar(r.Context(), company, conn, year)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"dev.tandem.ws/tandem/camper/pkg/auth"
|
||||
|
@ -94,6 +95,10 @@ func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, templ
|
|||
"dec": func(i int) int {
|
||||
return i - 1
|
||||
},
|
||||
"hexToDec": func(s string) int {
|
||||
num, _ := strconv.ParseInt(s, 16, 0)
|
||||
return int(num)
|
||||
},
|
||||
})
|
||||
templates = append(templates, "form.gohtml")
|
||||
files := make([]string, len(templates))
|
||||
|
|
|
@ -821,6 +821,94 @@ dt {
|
|||
}
|
||||
}
|
||||
|
||||
.campsite_type_calendar button {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.campsite_type_calendar form button:first-child, .campsite_type_calendar > header button {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.campsite_type_calendar > header {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.campsite_type_calendar > header button:first-of-type {
|
||||
order: -1;
|
||||
}
|
||||
|
||||
.campsite_type_calendar > header button:first-of-type::before {
|
||||
content: "←";
|
||||
}
|
||||
|
||||
.campsite_type_calendar > header button:last-of-type::before {
|
||||
content: "→";
|
||||
}
|
||||
|
||||
.campsite_type_calendar > div {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(27rem, 1fr));
|
||||
grid-auto-rows: 1fr;
|
||||
justify-content: center;
|
||||
align-items: start;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
.campsite_type_calendar > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.campsite_type_calendar table {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.campsite_type_calendar table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.campsite_type_calendar td {
|
||||
width: calc(100% / 7);
|
||||
}
|
||||
|
||||
.campsite_type_calendar time {
|
||||
--aa-brightness: calc(((var(--red) * 299) + (var(--green) * 587) + (var(--blue) * 114)) / 1000);
|
||||
--aa-color: calc((var(--aa-brightness) - 128) * -1000);
|
||||
background: rgb(var(--red), var(--green), var(--blue));
|
||||
color: rgb(var(--aa-color), var(--aa-color), var(--aa-color));
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 3rem;
|
||||
aspect-ratio: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.campsite_type_calendar [aria-checked] {
|
||||
border: 2px solid var(--camper--color--black);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.campsite_type_calendar [aria-checked]::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: block;
|
||||
background-color: var(--camper--color--black);
|
||||
border-radius: 50%;
|
||||
width: .8rem;
|
||||
height: .8rem;
|
||||
}
|
||||
|
||||
body > footer {
|
||||
display: flex;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<article class="season-calendar" data-hx-target="this" data-hx-swap="outerHTML">
|
||||
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/season.seasonCalendar*/ -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/season.Calendar*/ -}}
|
||||
<header>
|
||||
<h3>{{ .Year }}</h3>
|
||||
<button type="button"
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
<article class="campsite_type_calendar">
|
||||
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/season.Calendar*/ -}}
|
||||
<header>
|
||||
<h3>{{ .Year }}</h3>
|
||||
<button type="button"
|
||||
data-hx-get="/{{ currentLocale }}/seasons/calendar?year={{ dec .Year }}"
|
||||
><span class="sr-only">{{ pgettext "Prev" "navigation" }}</span></button>
|
||||
<button type="button"
|
||||
data-hx-get="/{{ currentLocale }}/seasons/calendar?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>
|
||||
<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 }}
|
||||
<tr>
|
||||
{{- range . }}
|
||||
<td>
|
||||
{{- if .Color -}}
|
||||
<time style="--red: {{ slice .Color 1 3 | hexToDec }}; --green: {{ slice .Color 3 5 | hexToDec }}; --blue: {{ slice .Color 5 7 | hexToDec }}"
|
||||
datetime="{{ .Date.Format "2006-01-02" }}"
|
||||
>{{ .Date.Format "2" }}</time>
|
||||
{{- end -}}
|
||||
</td>
|
||||
{{- end }}
|
||||
</tr>
|
||||
{{- end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{- end }}
|
||||
</div>
|
||||
</article>
|
|
@ -69,7 +69,7 @@
|
|||
</dt>
|
||||
{{ if .HasOptions -}}
|
||||
<dd>{{ printf (gettext "Starting from %s €/night") .PricePerNight }}</dd>
|
||||
{{- else -}}
|
||||
{{- else if .PricePerNight -}}
|
||||
<dd>{{ printf (gettext "%s €/night") .PricePerNight }}</dd>
|
||||
{{- end }}
|
||||
{{ if gt .MinNights 1 -}}
|
||||
|
@ -82,8 +82,7 @@
|
|||
{{- end }}
|
||||
</div>
|
||||
|
||||
<div class="campsite_type_calendar">
|
||||
</div>
|
||||
{{ template "calendar.gohtml" .Calendar }}
|
||||
|
||||
{{ with .Features -}}
|
||||
<article class="campsite_type_features">
|
||||
|
|
Loading…
Reference in New Issue