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

View File

@ -467,12 +467,12 @@ nav:last-of-type > ul > li:last-child {
padding: 1.5rem 2rem; 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; display: inline-block;
transition: transform 0.5s ease; 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); transform: translateX(1.3rem);
} }
@ -785,37 +785,39 @@ dt {
} }
} }
.campsite_type_booking form { .campsite_type_booking fieldset, .campsite_type_booking footer {
flex: .4; flex: 1;
}
.campsite_type_booking {
background-color: var(--accent); background-color: var(--accent);
padding: 2rem; padding: 2rem;
} }
.campsite_type_booking form fieldset { .campsite_type_booking fieldset {
display: flex; display: flex;
gap: 2.5rem; gap: 2.5rem;
padding: 0; padding: 0;
border: none; border: none;
} }
.campsite_type_booking form label { .campsite_type_booking label {
flex: 1; flex: 1;
font-size: 2rem; font-size: 2rem;
} }
.campsite_type_booking form input { .campsite_type_booking input {
padding: 1.5rem .5rem; padding: 1.5rem .5rem;
width: 100%; width: 100%;
background-color: var(--base); background-color: var(--base);
} }
.campsite_type_booking form footer { .campsite_type_booking footer {
margin-top: 2rem; margin-top: 2rem;
text-align: right;
} }
.campsite_type_booking form button { .campsite_type_booking button {
width: 100%;
text-align: left;
background-color: var(--clar); background-color: var(--clar);
padding: 1.5rem 2rem; padding: 1.5rem 2rem;
cursor: pointer; cursor: pointer;
@ -824,24 +826,35 @@ dt {
line-height: 0.9em; line-height: 0.9em;
} }
.campsite_type_booking form, .campsite_type_booking,
.campsite_type_booking form button, .campsite_type_booking button,
.campsite_type_booking form input { .campsite_type_booking input {
border: none; border: none;
border-radius: 5px; 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 { .campsite_type_prices {
flex: .6;
padding: 2.5rem; padding: 2.5rem;
border-radius: 5px; border-radius: 5px;
border: 3px solid black; flex: .5;
} }
.campsite_type_prices dl { .campsite_type_prices dl {
display: flex; display: flex;
justify-content: space-between; flex-direction: column;
gap: 1rem; gap: 1rem;
border-bottom: 1px solid black;
} }
.campsite_type_prices dl div:hover { .campsite_type_prices dl div:hover {
@ -850,18 +863,49 @@ dt {
.campsite_type_prices div { .campsite_type_prices div {
flex-basis: unset; flex-basis: unset;
min-height: 0;
padding: 0;
} }
.campsite_type_prices dt { .campsite_type_prices dt {
display: flex; display: flex;
align-items: center; align-items: center;
gap: .5rem; gap: 1.5rem;
border: none; border-top: 1px solid black;
padding: 0; 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 { .campsite_type_calendar {
padding: 5rem 0 2.5rem; padding: 2.5rem 0;
min-width: 0;
flex: 1;
} }
.campsite_type_features li { .campsite_type_features li {
@ -936,6 +980,7 @@ dt {
.campsite_type_calendar button { .campsite_type_calendar button {
display: flex; display: flex;
gap: 1em; gap: 1em;
font-size: 2.5rem;
border: none; border: none;
cursor: pointer; cursor: pointer;
} }

View File

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

View File

@ -9,6 +9,7 @@
{{ define "head" -}} {{ define "head" -}}
{{ template "carouselStyle" }} {{ template "carouselStyle" }}
<script src="/static/alpinejs@3.13.3.min.js" defer></script>
{{- end }} {{- end }}
{{ define "content" -}} {{ define "content" -}}
@ -33,8 +34,7 @@
</div> </div>
{{- end }} {{- end }}
<div class="campsite_type_booking"> <form action="/{{ currentLocale }}/booking" method="get" class="campsite_type_booking">
<form action="/{{ currentLocale }}/booking" method="get">
<input type="hidden" name="campsite_type" value="{{ .Slug }}"> <input type="hidden" name="campsite_type" value="{{ .Slug }}">
<fieldset> <fieldset>
<label> <label>
@ -55,34 +55,7 @@
</footer> </footer>
</form> </form>
{{ with .Prices -}} <div class="campsite_type_calendar_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"/>
</svg>
{{ .SeasonName }}
</dt>
{{ if .HasOptions -}}
<dd>{{ printf (gettext "Starting from %s €/night") .PricePerNight }}</dd>
{{- else if .PricePerNight -}}
<dd>{{ printf (gettext "%s €/night") .PricePerNight }}</dd>
{{- end }}
{{ if gt .MinNights 1 -}}
<dd>{{ printf (gettext "*Minimum %d nights per stay") .MinNights }}</dd>
{{- end }}
</div>
{{- end }}
</dl>
</article>
{{- end }}
</div>
<article class="campsite_type_calendar"> <article class="campsite_type_calendar">
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/season.Calendar*/ -}} {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/season.Calendar*/ -}}
<header> <header>
@ -95,6 +68,32 @@
</div> </div>
</article> </article>
{{ with .Prices -}}
<article class="campsite_type_prices">
<h3 class="sr-only">{{( pgettext "Prices" "title" )}}</h3>
<dl>
{{ range . -}}
<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>
<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 x-show="open">{{ printf (gettext "*Minimum %d nights per stay") .MinNights }}</dd>
{{- end }}
</div>
{{- end }}
</dl>
</article>
{{- end }}
</div>
{{ with .Features -}} {{ with .Features -}}
<article class="campsite_type_features"> <article class="campsite_type_features">
<h3 class="sr-only">{{( pgettext "Features" "title" )}}</h3> <h3 class="sr-only">{{( pgettext "Features" "title" )}}</h3>