Bring back the whole list of options in type page, but in accordion

This is how the customer wants it.
This commit is contained in:
jordi fita mas 2023-12-22 03:27:49 +01:00
parent 12d5356455
commit 4d0123def7
4 changed files with 140 additions and 88 deletions

View File

@ -7,6 +7,7 @@ package types
import (
"context"
"github.com/jackc/pgx/v4"
gotemplate "html/template"
"net/http"
"time"
@ -78,7 +79,12 @@ type typePrice struct {
SeasonColor string
MinNights int
PricePerNight string
HasOptions bool
Options []*optionPrice
}
type optionPrice struct {
OptionName string
PricePerNight string
}
type typeFeature struct {
@ -131,33 +137,29 @@ func collectPrices(ctx context.Context, conn *database.Conn, language language.T
select coalesce(i18n.name, season.name) as l10n_name
, to_color(season.color)::text
, coalesce(cost.min_nights, 1)
, to_price(coalesce(cost.cost_per_night, 0) + coalesce(option.cost_per_night, 0))
, option.cost_per_night is not null
, season.position
, to_price(coalesce(cost.cost_per_night, 0))
, array_agg((coalesce(option_i18n.name, option.name), to_price(coalesce(option_cost.cost_per_night, 0))) order by option.position) filter (where option.campsite_type_option_id is not null)
from season
left join season_i18n as i18n on season.season_id = i18n.season_id and i18n.lang_tag = $1
left join (
campsite_type_cost as cost join campsite_type as type on cost.campsite_type_id = type.campsite_type_id and type.slug = $2
) as cost on cost.season_id = season.season_id
left join (
select season_id
, sum(lower(range) * cost_per_night)::integer as cost_per_night
from campsite_type_option
join campsite_type using(campsite_type_id)
join campsite_type_option_cost using (campsite_type_option_id)
where slug = $2
group by season_id
) as option on option.season_id = season.season_id
select option.*
from campsite_type_option as option
join campsite_type as type on option.campsite_type_id = type.campsite_type_id and type.slug = $2
) as option on true
left join campsite_type_option_i18n as option_i18n on option_i18n.campsite_type_option_id = option.campsite_type_option_id and option_i18n.lang_tag = $1
left join campsite_type_option_cost as option_cost on option_cost.campsite_type_option_id = option.campsite_type_option_id and option_cost.season_id = season.season_id
where season.active
union all
select $3
, to_color($4)::text
, 1
, ''
, false
, 2147483647 as position
order by position, l10n_name
`, language, slug, locale.PgettextNoop("Closed", "season"), season.UnsetColor)
group by i18n.name
, season.name
, season.color
, cost.min_nights
, cost.cost_per_night
, season.position
order by season.position, l10n_name
`, pgx.QueryResultFormats{pgx.BinaryFormatCode}, language, slug)
if err != nil {
return nil, err
}
@ -165,10 +167,16 @@ func collectPrices(ctx context.Context, conn *database.Conn, language language.T
var prices []*typePrice
for rows.Next() {
price := &typePrice{}
var position int
if err := rows.Scan(&price.SeasonName, &price.SeasonColor, &price.MinNights, &price.PricePerNight, &price.HasOptions, &position); err != nil {
var options database.RecordArray
if err := rows.Scan(&price.SeasonName, &price.SeasonColor, &price.MinNights, &price.PricePerNight, &options); err != nil {
return nil, err
}
for _, el := range options.Elements {
price.Options = append(price.Options, &optionPrice{
OptionName: el.Fields[0].Get().(string),
PricePerNight: el.Fields[1].Get().(string),
})
}
prices = append(prices, price)
}
return prices, nil

View File

@ -467,12 +467,12 @@ nav:last-of-type > ul > li:last-child {
padding: 1.5rem 2rem;
}
.nature div:first-child a span, .services a span, .surroundings .spiel a:hover span, .campsite_type_booking form button span {
.nature div:first-child a span, .services a span, .surroundings .spiel a:hover span, .campsite_type_booking button span {
display: inline-block;
transition: transform 0.5s ease;
}
.nature div:first-child a:hover span, .services a:hover span, .spiel a:hover span, .campsite_type_booking form button:hover span {
.nature div:first-child a:hover span, .services a:hover span, .spiel a:hover span, .campsite_type_booking button:hover span {
transform: translateX(1.3rem);
}
@ -785,37 +785,39 @@ dt {
}
}
.campsite_type_booking form {
flex: .4;
.campsite_type_booking fieldset, .campsite_type_booking footer {
flex: 1;
}
.campsite_type_booking {
background-color: var(--accent);
padding: 2rem;
}
.campsite_type_booking form fieldset {
.campsite_type_booking fieldset {
display: flex;
gap: 2.5rem;
padding: 0;
border: none;
}
.campsite_type_booking form label {
.campsite_type_booking label {
flex: 1;
font-size: 2rem;
}
.campsite_type_booking form input {
.campsite_type_booking input {
padding: 1.5rem .5rem;
width: 100%;
background-color: var(--base);
}
.campsite_type_booking form footer {
.campsite_type_booking footer {
margin-top: 2rem;
text-align: right;
}
.campsite_type_booking form button {
width: 100%;
text-align: left;
.campsite_type_booking button {
background-color: var(--clar);
padding: 1.5rem 2rem;
cursor: pointer;
@ -824,24 +826,35 @@ dt {
line-height: 0.9em;
}
.campsite_type_booking form,
.campsite_type_booking form button,
.campsite_type_booking form input {
.campsite_type_booking,
.campsite_type_booking button,
.campsite_type_booking input {
border: none;
border-radius: 5px;
}
.campsite_type_calendar_prices {
display: flex;
flex-direction: row-reverse;
}
@media (max-width: 48rem) {
.campsite_type_calendar_prices {
flex-direction: column;
}
}
.campsite_type_prices {
flex: .6;
padding: 2.5rem;
border-radius: 5px;
border: 3px solid black;
flex: .5;
}
.campsite_type_prices dl {
display: flex;
justify-content: space-between;
flex-direction: column;
gap: 1rem;
border-bottom: 1px solid black;
}
.campsite_type_prices dl div:hover {
@ -850,18 +863,49 @@ dt {
.campsite_type_prices div {
flex-basis: unset;
min-height: 0;
padding: 0;
}
.campsite_type_prices dt {
display: flex;
align-items: center;
gap: .5rem;
border: none;
padding: 0;
gap: 1.5rem;
border-top: 1px solid black;
border-bottom: none;
padding: .5em 0 0;
cursor: pointer;
position: relative;
}
.campsite_type_prices dt::after {
position: absolute;
right: 0;
top: 50%;
content: '+';
width: 1em;
aspect-ratio: 1;
display: flex;
justify-content: center;
align-items: center;
background-color: black;
color: white;
border-radius: 50%;
line-height: 0;
}
.campsite_type_prices dt.open::after {
content: '-';
}
.campsite_type_prices dd {
padding: 0 0 0 calc(30px + 1.5rem);
}
.campsite_type_calendar {
padding: 5rem 0 2.5rem;
padding: 2.5rem 0;
min-width: 0;
flex: 1;
}
.campsite_type_features li {
@ -936,6 +980,7 @@ dt {
.campsite_type_calendar button {
display: flex;
gap: 1em;
font-size: 2.5rem;
border: none;
cursor: pointer;
}

View File

@ -11,7 +11,7 @@
<link rel="stylesheet" media="screen" href="/static/camper.css">
<link rel="stylesheet" media="screen" href="/static/icons.css">
<script src="/static/sortable@1.15.1.min.js"></script>
<script src="/static/alpinejs@3.13.3.min.js"></script>
<script src="/static/alpinejs@3.13.3.min.js" defer></script>
<script src="/static/htmx@1.9.3.min.js"></script>
<script type="module" src="/static/camper.js"></script>
{{ block "head" . }}{{ end }}

View File

@ -9,6 +9,7 @@
{{ define "head" -}}
{{ template "carouselStyle" }}
<script src="/static/alpinejs@3.13.3.min.js" defer></script>
{{- end }}
{{ define "content" -}}
@ -33,48 +34,58 @@
</div>
{{- end }}
<div class="campsite_type_booking">
<form action="/{{ currentLocale }}/booking" method="get">
<input type="hidden" name="campsite_type" value="{{ .Slug }}">
<fieldset>
<label>
{{( pgettext "Check-in Date" "input")}}
<br>
<input name="arrival_date" type="date" required>
<br>
</label>
<label>
{{( pgettext "Check-out Date" "input")}}
<br>
<input name="departure_date" type="date" required>
<br>
</label>
</fieldset>
<footer>
<button type="submit">{{( pgettext "Book" "action" )}} <span>→</span></button>
</footer>
</form>
<form action="/{{ currentLocale }}/booking" method="get" class="campsite_type_booking">
<input type="hidden" name="campsite_type" value="{{ .Slug }}">
<fieldset>
<label>
{{( pgettext "Check-in Date" "input")}}
<br>
<input name="arrival_date" type="date" required>
<br>
</label>
<label>
{{( pgettext "Check-out Date" "input")}}
<br>
<input name="departure_date" type="date" required>
<br>
</label>
</fieldset>
<footer>
<button type="submit">{{( pgettext "Book" "action" )}} <span>→</span></button>
</footer>
</form>
<div class="campsite_type_calendar_prices">
<article class="campsite_type_calendar">
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/season.Calendar*/ -}}
<header>
<h3 class="sr-only">{{( pgettext "Calendar" "title" )}}</h3>
<button type="button"><span class="sr-only">{{ pgettext "Prev" "navigation" }}</span></button>
<button type="button"><span class="sr-only">{{ pgettext "Next" "navigation" }}</span></button>
</header>
<div>
{{ template "calendar.gohtml" .Calendar }}
</div>
</article>
{{ with .Prices -}}
<article class="campsite_type_prices">
<h3 class="sr-only">{{( pgettext "Prices" "title" )}}</h3>
<dl>
{{ range . -}}
<div>
<dt>
<svg width="20px" height="20px">
<circle cx="50%" cy="50%" r="49%" fill="{{ .SeasonColor }}" stroke="#000"
stroke-width=".5"/>
<div x-data="{open: false}">
<dt @click="open = !open" :class="open && 'open'">
<svg width="30px" height="30px">
<circle cx="50%" cy="50%" r="49%" fill="{{ .SeasonColor }}"/>
</svg>
{{ .SeasonName }}
</dt>
{{ if .HasOptions -}}
<dd>{{ printf (gettext "Starting from %s €/night") .PricePerNight }}</dd>
{{- else if .PricePerNight -}}
<dd>{{ printf (gettext "%s €/night") .PricePerNight }}</dd>
<dd x-show="open">{{ printf (gettext "%s €/night") .PricePerNight }}</dd>
{{ range .Options }}
<dd x-show="open">{{ printf (gettext "%s: %s €/night") .OptionName .PricePerNight }}</dd>
{{- end }}
{{ if gt .MinNights 1 -}}
<dd>{{ printf (gettext "*Minimum %d nights per stay") .MinNights }}</dd>
<dd x-show="open">{{ printf (gettext "*Minimum %d nights per stay") .MinNights }}</dd>
{{- end }}
</div>
{{- end }}
@ -83,18 +94,6 @@
{{- end }}
</div>
<article class="campsite_type_calendar">
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/season.Calendar*/ -}}
<header>
<h3 class="sr-only">{{( pgettext "Calendar" "title" )}}</h3>
<button type="button"><span class="sr-only">{{ pgettext "Prev" "navigation" }}</span></button>
<button type="button"><span class="sr-only">{{ pgettext "Next" "navigation" }}</span></button>
</header>
<div>
{{ template "calendar.gohtml" .Calendar }}
</div>
</article>
{{ with .Features -}}
<article class="campsite_type_features">
<h3 class="sr-only">{{( pgettext "Features" "title" )}}</h3>