Add campsite map in SVG

I intend to use the same SVG file for customers and employees, so i had
to change Oriol’s design to add a class to layers that are supposed to
be only for customers, like trees.  These are hidden in the admin area.

I understood that customers and employees have to click on a campsite to
select it, and then they can book or whatever they need to do to them.
Since customers and employees most certainly will need to have different
listeners on campsites, i decided to add the link with JavaScript.  To
do so, i need a custom XML attribute with the campsite’s identifier.

Since i have seen that all campsites have a label, i changed the
“identifier” to the unique combination (company_id, label).  The
company_id is there because different companies could have the same
label; i left the campsite_id primary key for foreign constraints.

In this case, as a test, i add an <a> element to the campsite with a
link to edit it; we’ll discuss with Oriol what exactly it needs to do.

However, the original design had the labels in a different layer, that
interfered with the link, as the numbers must be above the path and
the link must wrap the path in order to “inherit” its shape.  I had no
other recourse than to move the labels in the same layer as the paths’.
This commit is contained in:
jordi fita mas 2023-09-24 03:17:13 +02:00
parent 8c0dbf7806
commit e3503187d3
19 changed files with 3377 additions and 114 deletions

View File

@ -99,6 +99,86 @@ values (72, 'en', 'Plots', '')
, (75, 'es', 'Cabañas de madera', '') , (75, 'es', 'Cabañas de madera', '')
; ;
select add_campsite(72, '2');
select add_campsite(72, '3');
select add_campsite(72, '4');
select add_campsite(72, '5');
select add_campsite(72, '6');
select add_campsite(72, '7');
select add_campsite(72, '8');
select add_campsite(72, '9');
select add_campsite(72, '10');
select add_campsite(72, '11');
select add_campsite(72, '12');
select add_campsite(72, '14');
select add_campsite(72, '15');
select add_campsite(72, '16');
select add_campsite(72, '17');
select add_campsite(72, '18');
select add_campsite(72, '19');
select add_campsite(72, '20');
select add_campsite(72, '21');
select add_campsite(72, '22');
select add_campsite(72, '23');
select add_campsite(72, '24');
select add_campsite(72, '25');
select add_campsite(72, '26');
select add_campsite(72, '27');
select add_campsite(72, '28');
select add_campsite(72, '29');
select add_campsite(72, '42');
select add_campsite(72, '43');
select add_campsite(72, '44');
select add_campsite(72, '45');
select add_campsite(72, '46');
select add_campsite(72, '47');
select add_campsite(72, '48');
select add_campsite(72, '50');
select add_campsite(72, '51');
select add_campsite(72, '52');
select add_campsite(72, '53');
select add_campsite(72, '54');
select add_campsite(72, '55');
select add_campsite(72, '56');
select add_campsite(72, '57');
select add_campsite(72, '58');
select add_campsite(72, '59');
select add_campsite(72, '60');
select add_campsite(72, '61');
select add_campsite(72, '62');
select add_campsite(72, '63');
select add_campsite(72, '64');
select add_campsite(72, '65');
select add_campsite(72, '69');
select add_campsite(72, '70');
select add_campsite(72, '71');
select add_campsite(72, '72');
select add_campsite(72, '73');
select add_campsite(72, '74');
select add_campsite(72, '75');
select add_campsite(72, '76');
select add_campsite(72, '77');
select add_campsite(72, '79');
select add_campsite(72, '80');
select add_campsite(72, '81');
select add_campsite(72, '82');
select add_campsite(72, '83');
select add_campsite(72, '89');
select add_campsite(72, '90');
select add_campsite(72, '91');
select add_campsite(72, '92');
select add_campsite(72, '93');
select add_campsite(72, '94');
select add_campsite(72, '95');
select add_campsite(72, '96');
select add_campsite(72, '97');
select add_campsite(72, '98');
select add_campsite(72, 'B1');
select add_campsite(72, 'D1');
select add_campsite(72, 'D2');
select add_campsite(72, 'D3');
select add_campsite(72, 'D4');
alter sequence service_service_id_seq restart with 82; alter sequence service_service_id_seq restart with 82;
insert into service (company_id, icon_name, name, description) insert into service (company_id, icon_name, name, description)
values (52, 'information', 'Informació', '<p>A la recepció linformarem del que pot fer des del càmping mateix o pels voltants.</p>') values (52, 'information', 'Informació', '<p>A la recepció linformarem del que pot fer des del càmping mateix o pels voltants.</p>')

View File

@ -8,21 +8,21 @@ begin;
set search_path to camper, public; set search_path to camper, public;
create or replace function add_campsite(campsite_type integer, label text) returns uuid as create or replace function add_campsite(campsite_type integer, label text) returns integer as
$$ $$
declare declare
campsite_slug uuid; cid integer;
begin begin
insert into campsite (company_id, campsite_type_id, label) insert into campsite (company_id, campsite_type_id, label)
select company_id, campsite_type_id, label select company_id, campsite_type_id, label
from campsite_type from campsite_type
where campsite_type_id = add_campsite.campsite_type where campsite_type_id = add_campsite.campsite_type
returning slug into campsite_slug returning campsite_id into cid
; ;
if campsite_slug is null then if cid is null then
raise foreign_key_violation using message = 'insert or update on table "campsite" violates foreign key constraint "campsite_campsite_type_id_fkey"'; raise foreign_key_violation using message = 'insert or update on table "campsite" violates foreign key constraint "campsite_campsite_type_id_fkey"';
end if; end if;
return campsite_slug; return cid;
end end
$$ $$
language plpgsql language plpgsql

View File

@ -12,10 +12,10 @@ set search_path to camper, public;
create table campsite ( create table campsite (
campsite_id serial primary key, campsite_id serial primary key,
company_id integer not null references company, company_id integer not null references company,
slug uuid unique not null default gen_random_uuid(),
campsite_type_id integer not null references campsite_type,
label text not null constraint label_not_empty check(length(trim(label)) > 0), label text not null constraint label_not_empty check(length(trim(label)) > 0),
active boolean not null default true campsite_type_id integer not null references campsite_type,
active boolean not null default true,
unique (company_id, label)
); );
grant select on table campsite to guest; grant select on table campsite to guest;

View File

@ -7,19 +7,19 @@ begin;
set search_path to camper, public; set search_path to camper, public;
create or replace function edit_campsite(slug uuid, campsite_type integer, label text, active boolean) returns uuid as create or replace function edit_campsite(campsite_id integer, campsite_type integer, new_label text, active boolean) returns integer as
$$ $$
update campsite update campsite
set label = edit_campsite.label set label = edit_campsite.new_label
, campsite_type_id = edit_campsite.campsite_type , campsite_type_id = edit_campsite.campsite_type
, active = edit_campsite.active , active = edit_campsite.active
where slug = edit_campsite.slug where campsite_id = edit_campsite.campsite_id
returning slug; returning campsite_id;
$$ $$
language sql language sql
; ;
revoke execute on function edit_campsite(uuid, integer, text, boolean) from public; revoke execute on function edit_campsite(integer, integer, text, boolean) from public;
grant execute on function edit_campsite(uuid, integer, text, boolean) to admin; grant execute on function edit_campsite(integer, integer, text, boolean) to admin;
commit; commit;

View File

@ -16,7 +16,6 @@ import (
httplib "dev.tandem.ws/tandem/camper/pkg/http" httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/locale" "dev.tandem.ws/tandem/camper/pkg/locale"
"dev.tandem.ws/tandem/camper/pkg/template" "dev.tandem.ws/tandem/camper/pkg/template"
"dev.tandem.ws/tandem/camper/pkg/uuid"
) )
type AdminHandler struct { type AdminHandler struct {
@ -55,25 +54,28 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost) httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
} }
default: default:
if !uuid.Valid(head) {
http.NotFound(w, r)
return
}
f := newCampsiteForm(r.Context(), conn) f := newCampsiteForm(r.Context(), conn)
if err := f.FillFromDatabase(r.Context(), conn, head); err != nil { if err := f.FillFromDatabase(r.Context(), conn, company, head); err != nil {
if database.ErrorIsNotFound(err) { if database.ErrorIsNotFound(err) {
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
panic(err) panic(err)
} }
switch r.Method {
case http.MethodGet: head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
f.MustRender(w, r, user, company) switch head {
case http.MethodPut: case "":
editCampsite(w, r, user, company, conn, f) switch r.Method {
case http.MethodGet:
f.MustRender(w, r, user, company)
case http.MethodPut:
editCampsite(w, r, user, company, conn, f)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
}
default: default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut) http.NotFound(w, r)
} }
} }
} }
@ -92,8 +94,7 @@ func serveCampsiteIndex(w http.ResponseWriter, r *http.Request, user *auth.User,
func collectCampsiteEntries(ctx context.Context, company *auth.Company, conn *database.Conn) ([]*campsiteEntry, error) { func collectCampsiteEntries(ctx context.Context, company *auth.Company, conn *database.Conn) ([]*campsiteEntry, error) {
rows, err := conn.Query(ctx, ` rows, err := conn.Query(ctx, `
select campsite.slug select campsite.label
, campsite.label
, campsite_type.name , campsite_type.name
, campsite.active , campsite.active
from campsite from campsite
@ -108,7 +109,7 @@ func collectCampsiteEntries(ctx context.Context, company *auth.Company, conn *da
var campsites []*campsiteEntry var campsites []*campsiteEntry
for rows.Next() { for rows.Next() {
entry := &campsiteEntry{} entry := &campsiteEntry{}
if err = rows.Scan(&entry.Slug, &entry.Label, &entry.Type, &entry.Active); err != nil { if err = rows.Scan(&entry.Label, &entry.Type, &entry.Active); err != nil {
return nil, err return nil, err
} }
campsites = append(campsites, entry) campsites = append(campsites, entry)
@ -118,7 +119,6 @@ func collectCampsiteEntries(ctx context.Context, company *auth.Company, conn *da
} }
type campsiteEntry struct { type campsiteEntry struct {
Slug string
Label string Label string
Type string Type string
Active bool Active bool
@ -129,7 +129,7 @@ type campsiteIndex struct {
} }
func (page *campsiteIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { func (page *campsiteIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "campsite/index.gohtml", page) template.MustRenderAdminFiles(w, r, user, company, page, "campsite/index.gohtml", "web/templates/campsite_map.svg")
} }
func addCampsite(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { func addCampsite(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
@ -169,12 +169,13 @@ func editCampsite(w http.ResponseWriter, r *http.Request, user *auth.User, compa
f.MustRender(w, r, user, company) f.MustRender(w, r, user, company)
return return
} }
conn.MustExec(r.Context(), "select edit_campsite($1, $2, $3, $4)", f.Slug, f.CampsiteType, f.Label, f.Active) conn.MustExec(r.Context(), "select edit_campsite($1, $2, $3, $4)", f.ID, f.CampsiteType, f.Label, f.Active)
httplib.Redirect(w, r, "/admin/campsites", http.StatusSeeOther) httplib.Redirect(w, r, "/admin/campsites", http.StatusSeeOther)
} }
type campsiteForm struct { type campsiteForm struct {
Slug string ID int
CurrentLabel string
Active *form.Checkbox Active *form.Checkbox
CampsiteType *form.Select CampsiteType *form.Select
Label *form.Input Label *form.Input
@ -197,10 +198,17 @@ func newCampsiteForm(ctx context.Context, conn *database.Conn) *campsiteForm {
} }
} }
func (f *campsiteForm) FillFromDatabase(ctx context.Context, conn *database.Conn, slug string) error { func (f *campsiteForm) FillFromDatabase(ctx context.Context, conn *database.Conn, company *auth.Company, label string) error {
f.Slug = slug f.CurrentLabel = label
row := conn.QueryRow(ctx, "select array[campsite_type_id::text], label, active from campsite where slug = $1", slug) row := conn.QueryRow(ctx, `
return row.Scan(&f.CampsiteType.Selected, &f.Label.Val, &f.Active.Checked) select campsite_id
, array[campsite_type_id::text]
, label
, active
from campsite
where company_id = $1
and label = $2`, company.ID, label)
return row.Scan(&f.ID, &f.CampsiteType.Selected, &f.Label.Val, &f.Active.Checked)
} }
func (f *campsiteForm) Parse(r *http.Request) error { func (f *campsiteForm) Parse(r *http.Request) error {

View File

@ -33,6 +33,15 @@ func MustRenderAdmin(w io.Writer, r *http.Request, user *auth.User, company *aut
mustRenderLayout(w, user, company, adminTemplateFile, data, layout, filename) mustRenderLayout(w, user, company, adminTemplateFile, data, layout, filename)
} }
func MustRenderAdminFiles(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, data interface{}, filenames ...string) {
layout := "layout.gohtml"
if httplib.IsHTMxRequest(r) {
layout = "htmx.gohtml"
}
filenames = append([]string{layout}, filenames...)
mustRenderLayout(w, user, company, adminTemplateFile, data, filenames...)
}
func MustRenderNoLayout(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) { func MustRenderNoLayout(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
mustRenderLayout(w, user, company, adminTemplateFile, data, filename) mustRenderLayout(w, user, company, adminTemplateFile, data, filename)
} }
@ -73,7 +82,11 @@ func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, templ
templates = append(templates, "form.gohtml") templates = append(templates, "form.gohtml")
files := make([]string, len(templates)) files := make([]string, len(templates))
for i, tmpl := range templates { for i, tmpl := range templates {
files[i] = templateFile(tmpl) if len(tmpl) > 4 && tmpl[0] == 'w' && tmpl[1] == 'e' && tmpl[2] == 'b' && tmpl[3] == '/' {
files[i] = tmpl
} else {
files[i] = templateFile(tmpl)
}
} }
if _, err := t.ParseFiles(files...); err != nil { if _, err := t.ParseFiles(files...); err != nil {
panic(err) panic(err)

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: camper\n" "Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-09-22 02:14+0200\n" "POT-Creation-Date: 2023-09-24 03:04+0200\n"
"PO-Revision-Date: 2023-07-22 23:45+0200\n" "PO-Revision-Date: 2023-07-22 23:45+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n" "Language-Team: Catalan <ca@dodds.net>\n"
@ -217,7 +217,7 @@ msgid "New Campsite"
msgstr "Nou allotjament" msgstr "Nou allotjament"
#: web/templates/admin/campsite/form.gohtml:38 #: web/templates/admin/campsite/form.gohtml:38
#: web/templates/admin/campsite/index.gohtml:20 #: web/templates/admin/campsite/index.gohtml:21
msgctxt "campsite" msgctxt "campsite"
msgid "Active" msgid "Active"
msgstr "Actiu" msgstr "Actiu"
@ -238,7 +238,7 @@ msgstr "Etiqueta"
#: web/templates/admin/campsite/index.gohtml:6 #: web/templates/admin/campsite/index.gohtml:6
#: web/templates/admin/campsite/index.gohtml:13 #: web/templates/admin/campsite/index.gohtml:13
#: web/templates/admin/layout.gohtml:70 #: web/templates/admin/layout.gohtml:52 web/templates/admin/layout.gohtml:73
msgctxt "title" msgctxt "title"
msgid "Campsites" msgid "Campsites"
msgstr "Allotjaments" msgstr "Allotjaments"
@ -248,29 +248,29 @@ msgctxt "action"
msgid "Add Campsite" msgid "Add Campsite"
msgstr "Afegeix allotjament" msgstr "Afegeix allotjament"
#: web/templates/admin/campsite/index.gohtml:18 #: web/templates/admin/campsite/index.gohtml:19
msgctxt "header" msgctxt "header"
msgid "Label" msgid "Label"
msgstr "Etiqueta" msgstr "Etiqueta"
#: web/templates/admin/campsite/index.gohtml:19 #: web/templates/admin/campsite/index.gohtml:20
msgctxt "header" msgctxt "header"
msgid "Type" msgid "Type"
msgstr "Tipus" msgstr "Tipus"
#: web/templates/admin/campsite/index.gohtml:28 #: web/templates/admin/campsite/index.gohtml:29
#: web/templates/admin/campsite/type/index.gohtml:36 #: web/templates/admin/campsite/type/index.gohtml:36
#: web/templates/admin/season/index.gohtml:32 #: web/templates/admin/season/index.gohtml:32
msgid "Yes" msgid "Yes"
msgstr "Sí" msgstr "Sí"
#: web/templates/admin/campsite/index.gohtml:28 #: web/templates/admin/campsite/index.gohtml:29
#: web/templates/admin/campsite/type/index.gohtml:36 #: web/templates/admin/campsite/type/index.gohtml:36
#: web/templates/admin/season/index.gohtml:32 #: web/templates/admin/season/index.gohtml:32
msgid "No" msgid "No"
msgstr "No" msgstr "No"
#: web/templates/admin/campsite/index.gohtml:34 #: web/templates/admin/campsite/index.gohtml:35
msgid "No campsites added yet." msgid "No campsites added yet."
msgstr "No sha afegit cap allotjament encara." msgstr "No sha afegit cap allotjament encara."
@ -308,7 +308,7 @@ msgstr "Descripció"
#: web/templates/admin/campsite/type/index.gohtml:6 #: web/templates/admin/campsite/type/index.gohtml:6
#: web/templates/admin/campsite/type/index.gohtml:13 #: web/templates/admin/campsite/type/index.gohtml:13
#: web/templates/admin/layout.gohtml:67 #: web/templates/admin/layout.gohtml:70
msgctxt "title" msgctxt "title"
msgid "Campsite Types" msgid "Campsite Types"
msgstr "Tipus dallotjaments" msgstr "Tipus dallotjaments"
@ -366,7 +366,7 @@ msgstr "Color"
#: web/templates/admin/season/index.gohtml:6 #: web/templates/admin/season/index.gohtml:6
#: web/templates/admin/season/index.gohtml:13 #: web/templates/admin/season/index.gohtml:13
#: web/templates/admin/layout.gohtml:73 #: web/templates/admin/layout.gohtml:76
msgctxt "title" msgctxt "title"
msgid "Seasons" msgid "Seasons"
msgstr "Temporades" msgstr "Temporades"
@ -413,7 +413,7 @@ msgid "Login"
msgstr "Entra" msgstr "Entra"
#: web/templates/admin/services/index.gohtml:6 #: web/templates/admin/services/index.gohtml:6
#: web/templates/admin/layout.gohtml:79 #: web/templates/admin/layout.gohtml:82
msgctxt "title" msgctxt "title"
msgid "Services Page" msgid "Services Page"
msgstr "Pàgina de serveis" msgstr "Pàgina de serveis"
@ -493,7 +493,7 @@ msgstr "Desa els canvis"
#: web/templates/admin/taxDetails.gohtml:6 #: web/templates/admin/taxDetails.gohtml:6
#: web/templates/admin/taxDetails.gohtml:13 #: web/templates/admin/taxDetails.gohtml:13
#: web/templates/admin/layout.gohtml:64 #: web/templates/admin/layout.gohtml:67
msgctxt "title" msgctxt "title"
msgid "Tax Details" msgid "Tax Details"
msgstr "Configuració fiscal" msgstr "Configuració fiscal"
@ -579,12 +579,12 @@ msgctxt "action"
msgid "Logout" msgid "Logout"
msgstr "Surt" msgstr "Surt"
#: web/templates/admin/layout.gohtml:76 web/templates/admin/home/index.gohtml:6 #: web/templates/admin/layout.gohtml:79 web/templates/admin/home/index.gohtml:6
msgctxt "title" msgctxt "title"
msgid "Home Page" msgid "Home Page"
msgstr "Pàgina dinici" msgstr "Pàgina dinici"
#: web/templates/admin/layout.gohtml:82 #: web/templates/admin/layout.gohtml:85
#: web/templates/admin/media/index.gohtml:6 #: web/templates/admin/media/index.gohtml:6
#: web/templates/admin/media/index.gohtml:12 #: web/templates/admin/media/index.gohtml:12
msgctxt "title" msgctxt "title"
@ -619,17 +619,17 @@ msgctxt "action"
msgid "Upload" msgid "Upload"
msgstr "Puja" msgstr "Puja"
#: web/templates/admin/media/picker.gohtml:44 #: web/templates/admin/media/picker.gohtml:47
msgctxt "title" msgctxt "title"
msgid "Choose Existing Media" msgid "Choose Existing Media"
msgstr "Elecció dun mèdia existent" msgstr "Elecció dun mèdia existent"
#: web/templates/admin/media/picker.gohtml:55 #: web/templates/admin/media/picker.gohtml:58
#: web/templates/admin/media/index.gohtml:21 #: web/templates/admin/media/index.gohtml:21
msgid "No media uploaded yet." msgid "No media uploaded yet."
msgstr "No sha pujat cap mèdia encara." msgstr "No sha pujat cap mèdia encara."
#: web/templates/admin/media/picker.gohtml:58 #: web/templates/admin/media/picker.gohtml:61
msgctxt "action" msgctxt "action"
msgid "Cancel" msgid "Cancel"
msgstr "Canceŀla" msgstr "Canceŀla"
@ -739,11 +739,11 @@ msgstr "No podeu deixar la imatge de portada en blanc."
msgid "Cover image must be an image media type." msgid "Cover image must be an image media type."
msgstr "La imatge de portada ha de ser un mèdia de tipus imatge." msgstr "La imatge de portada ha de ser un mèdia de tipus imatge."
#: pkg/campsite/admin.go:218 #: pkg/campsite/admin.go:226
msgid "Selected campsite type is not valid." msgid "Selected campsite type is not valid."
msgstr "El tipus dallotjament escollit no és vàlid." msgstr "El tipus dallotjament escollit no és vàlid."
#: pkg/campsite/admin.go:219 #: pkg/campsite/admin.go:227
msgid "Label can not be empty." msgid "Label can not be empty."
msgstr "No podeu deixar letiqueta en blanc." msgstr "No podeu deixar letiqueta en blanc."

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: camper\n" "Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-09-22 02:16+0200\n" "POT-Creation-Date: 2023-09-24 03:04+0200\n"
"PO-Revision-Date: 2023-07-22 23:46+0200\n" "PO-Revision-Date: 2023-07-22 23:46+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n" "Language-Team: Spanish <es@tp.org.es>\n"
@ -217,7 +217,7 @@ msgid "New Campsite"
msgstr "Nuevo alojamiento" msgstr "Nuevo alojamiento"
#: web/templates/admin/campsite/form.gohtml:38 #: web/templates/admin/campsite/form.gohtml:38
#: web/templates/admin/campsite/index.gohtml:20 #: web/templates/admin/campsite/index.gohtml:21
msgctxt "campsite" msgctxt "campsite"
msgid "Active" msgid "Active"
msgstr "Activo" msgstr "Activo"
@ -238,7 +238,7 @@ msgstr "Etiqueta"
#: web/templates/admin/campsite/index.gohtml:6 #: web/templates/admin/campsite/index.gohtml:6
#: web/templates/admin/campsite/index.gohtml:13 #: web/templates/admin/campsite/index.gohtml:13
#: web/templates/admin/layout.gohtml:70 #: web/templates/admin/layout.gohtml:52 web/templates/admin/layout.gohtml:73
msgctxt "title" msgctxt "title"
msgid "Campsites" msgid "Campsites"
msgstr "Alojamientos" msgstr "Alojamientos"
@ -248,29 +248,29 @@ msgctxt "action"
msgid "Add Campsite" msgid "Add Campsite"
msgstr "Añadir alojamiento" msgstr "Añadir alojamiento"
#: web/templates/admin/campsite/index.gohtml:18 #: web/templates/admin/campsite/index.gohtml:19
msgctxt "header" msgctxt "header"
msgid "Label" msgid "Label"
msgstr "Etiqueta" msgstr "Etiqueta"
#: web/templates/admin/campsite/index.gohtml:19 #: web/templates/admin/campsite/index.gohtml:20
msgctxt "header" msgctxt "header"
msgid "Type" msgid "Type"
msgstr "Tipo" msgstr "Tipo"
#: web/templates/admin/campsite/index.gohtml:28 #: web/templates/admin/campsite/index.gohtml:29
#: web/templates/admin/campsite/type/index.gohtml:36 #: web/templates/admin/campsite/type/index.gohtml:36
#: web/templates/admin/season/index.gohtml:32 #: web/templates/admin/season/index.gohtml:32
msgid "Yes" msgid "Yes"
msgstr "Sí" msgstr "Sí"
#: web/templates/admin/campsite/index.gohtml:28 #: web/templates/admin/campsite/index.gohtml:29
#: web/templates/admin/campsite/type/index.gohtml:36 #: web/templates/admin/campsite/type/index.gohtml:36
#: web/templates/admin/season/index.gohtml:32 #: web/templates/admin/season/index.gohtml:32
msgid "No" msgid "No"
msgstr "No" msgstr "No"
#: web/templates/admin/campsite/index.gohtml:34 #: web/templates/admin/campsite/index.gohtml:35
msgid "No campsites added yet." msgid "No campsites added yet."
msgstr "No se ha añadido ningún alojamiento todavía." msgstr "No se ha añadido ningún alojamiento todavía."
@ -308,7 +308,7 @@ msgstr "Descripción"
#: web/templates/admin/campsite/type/index.gohtml:6 #: web/templates/admin/campsite/type/index.gohtml:6
#: web/templates/admin/campsite/type/index.gohtml:13 #: web/templates/admin/campsite/type/index.gohtml:13
#: web/templates/admin/layout.gohtml:67 #: web/templates/admin/layout.gohtml:70
msgctxt "title" msgctxt "title"
msgid "Campsite Types" msgid "Campsite Types"
msgstr "Tipos de alojamientos" msgstr "Tipos de alojamientos"
@ -366,7 +366,7 @@ msgstr "Color"
#: web/templates/admin/season/index.gohtml:6 #: web/templates/admin/season/index.gohtml:6
#: web/templates/admin/season/index.gohtml:13 #: web/templates/admin/season/index.gohtml:13
#: web/templates/admin/layout.gohtml:73 #: web/templates/admin/layout.gohtml:76
msgctxt "title" msgctxt "title"
msgid "Seasons" msgid "Seasons"
msgstr "Temporadas" msgstr "Temporadas"
@ -413,7 +413,7 @@ msgid "Login"
msgstr "Entrar" msgstr "Entrar"
#: web/templates/admin/services/index.gohtml:6 #: web/templates/admin/services/index.gohtml:6
#: web/templates/admin/layout.gohtml:79 #: web/templates/admin/layout.gohtml:82
msgctxt "title" msgctxt "title"
msgid "Services Page" msgid "Services Page"
msgstr "Página de servicios" msgstr "Página de servicios"
@ -493,7 +493,7 @@ msgstr "Guardar los cambios"
#: web/templates/admin/taxDetails.gohtml:6 #: web/templates/admin/taxDetails.gohtml:6
#: web/templates/admin/taxDetails.gohtml:13 #: web/templates/admin/taxDetails.gohtml:13
#: web/templates/admin/layout.gohtml:64 #: web/templates/admin/layout.gohtml:67
msgctxt "title" msgctxt "title"
msgid "Tax Details" msgid "Tax Details"
msgstr "Configuración fiscal" msgstr "Configuración fiscal"
@ -579,12 +579,12 @@ msgctxt "action"
msgid "Logout" msgid "Logout"
msgstr "Salir" msgstr "Salir"
#: web/templates/admin/layout.gohtml:76 web/templates/admin/home/index.gohtml:6 #: web/templates/admin/layout.gohtml:79 web/templates/admin/home/index.gohtml:6
msgctxt "title" msgctxt "title"
msgid "Home Page" msgid "Home Page"
msgstr "Página de inicio" msgstr "Página de inicio"
#: web/templates/admin/layout.gohtml:82 #: web/templates/admin/layout.gohtml:85
#: web/templates/admin/media/index.gohtml:6 #: web/templates/admin/media/index.gohtml:6
#: web/templates/admin/media/index.gohtml:12 #: web/templates/admin/media/index.gohtml:12
msgctxt "title" msgctxt "title"
@ -619,17 +619,17 @@ msgctxt "action"
msgid "Upload" msgid "Upload"
msgstr "Subir" msgstr "Subir"
#: web/templates/admin/media/picker.gohtml:44 #: web/templates/admin/media/picker.gohtml:47
msgctxt "title" msgctxt "title"
msgid "Choose Existing Media" msgid "Choose Existing Media"
msgstr "Elección de un medio existente" msgstr "Elección de un medio existente"
#: web/templates/admin/media/picker.gohtml:55 #: web/templates/admin/media/picker.gohtml:58
#: web/templates/admin/media/index.gohtml:21 #: web/templates/admin/media/index.gohtml:21
msgid "No media uploaded yet." msgid "No media uploaded yet."
msgstr "No se ha subido ningún medio todavía." msgstr "No se ha subido ningún medio todavía."
#: web/templates/admin/media/picker.gohtml:58 #: web/templates/admin/media/picker.gohtml:61
msgctxt "action" msgctxt "action"
msgid "Cancel" msgid "Cancel"
msgstr "Cancelar" msgstr "Cancelar"
@ -739,11 +739,11 @@ msgstr "No podéis dejar la imagen de portada en blanco."
msgid "Cover image must be an image media type." msgid "Cover image must be an image media type."
msgstr "La imagen de portada tiene que ser un medio de tipo imagen." msgstr "La imagen de portada tiene que ser un medio de tipo imagen."
#: pkg/campsite/admin.go:218 #: pkg/campsite/admin.go:226
msgid "Selected campsite type is not valid." msgid "Selected campsite type is not valid."
msgstr "El tipo de alojamiento escogido no es válido." msgstr "El tipo de alojamiento escogido no es válido."
#: pkg/campsite/admin.go:219 #: pkg/campsite/admin.go:227
msgid "Label can not be empty." msgid "Label can not be empty."
msgstr "No podéis dejar la etiqueta en blanco." msgstr "No podéis dejar la etiqueta en blanco."

View File

@ -2,6 +2,6 @@
begin; begin;
drop function if exists camper.edit_campsite(uuid, integer, text, boolean); drop function if exists camper.edit_campsite(integer, integer, text, boolean);
commit; commit;

View File

@ -11,7 +11,7 @@ select plan(14);
select has_function('camper', 'add_campsite', array ['integer', 'text']); select has_function('camper', 'add_campsite', array ['integer', 'text']);
select function_lang_is('camper', 'add_campsite', array ['integer', 'text'], 'plpgsql'); select function_lang_is('camper', 'add_campsite', array ['integer', 'text'], 'plpgsql');
select function_returns('camper', 'add_campsite', array ['integer', 'text'], 'uuid'); select function_returns('camper', 'add_campsite', array ['integer', 'text'], 'integer');
select isnt_definer('camper', 'add_campsite', array ['integer', 'text']); select isnt_definer('camper', 'add_campsite', array ['integer', 'text']);
select volatility_is('camper', 'add_campsite', array ['integer', 'text'], 'volatile'); select volatility_is('camper', 'add_campsite', array ['integer', 'text'], 'volatile');
select function_privs_are('camper', 'add_campsite', array ['integer', 'text'], 'guest', array[]::text[]); select function_privs_are('camper', 'add_campsite', array ['integer', 'text'], 'guest', array[]::text[]);

View File

@ -5,12 +5,13 @@ reset client_min_messages;
begin; begin;
select plan(58); select plan(53);
set search_path to camper, public; set search_path to camper, public;
select has_table('campsite'); select has_table('campsite');
select has_pk('campsite'); select has_pk('campsite');
select col_is_unique('campsite', array['company_id', 'label']);
select table_privs_are('campsite', 'guest', array['SELECT']); select table_privs_are('campsite', 'guest', array['SELECT']);
select table_privs_are('campsite', 'employee', array['SELECT']); select table_privs_are('campsite', 'employee', array['SELECT']);
select table_privs_are('campsite', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']); select table_privs_are('campsite', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
@ -36,13 +37,6 @@ select col_type_is('campsite', 'company_id', 'integer');
select col_not_null('campsite', 'company_id'); select col_not_null('campsite', 'company_id');
select col_hasnt_default('campsite', 'company_id'); select col_hasnt_default('campsite', 'company_id');
select has_column('campsite', 'slug');
select col_is_unique('campsite', 'slug');
select col_type_is('campsite', 'slug', 'uuid');
select col_not_null('campsite', 'slug');
select col_has_default('campsite', 'slug');
select col_default_is('campsite', 'slug', 'gen_random_uuid()');
select has_column('campsite', 'campsite_type_id'); select has_column('campsite', 'campsite_type_id');
select col_is_fk('campsite', 'campsite_type_id'); select col_is_fk('campsite', 'campsite_type_id');
select fk_ok('campsite', 'campsite_type_id', 'campsite_type', 'campsite_type_id'); select fk_ok('campsite', 'campsite_type_id', 'campsite_type', 'campsite_type_id');

View File

@ -9,15 +9,15 @@ set search_path to camper, public;
select plan(12); select plan(12);
select has_function('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean']); select has_function('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean']);
select function_lang_is('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean'], 'sql'); select function_lang_is('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean'], 'sql');
select function_returns('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean'], 'uuid'); select function_returns('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean'], 'integer');
select isnt_definer('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean']); select isnt_definer('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean']);
select volatility_is('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean'], 'volatile'); select volatility_is('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean'], 'volatile');
select function_privs_are('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean'], 'guest', array[]::text[]); select function_privs_are('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean'], 'guest', array[]::text[]);
select function_privs_are('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean'], 'employee', array[]::text[]); select function_privs_are('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean'], 'employee', array[]::text[]);
select function_privs_are('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean'], 'admin', array['EXECUTE']); select function_privs_are('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean'], 'authenticator', array[]::text[]); select function_privs_are('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean'], 'authenticator', array[]::text[]);
set client_min_messages to warning; set client_min_messages to warning;
truncate campsite cascade; truncate campsite cascade;
@ -46,25 +46,25 @@ values (11, 1, 3, 'Type A')
, (13, 1, 3, 'Type C') , (13, 1, 3, 'Type C')
; ;
insert into campsite (company_id, campsite_type_id, slug, label, active) insert into campsite (campsite_id, company_id, campsite_type_id, label, active)
values (1, 11, '87452b88-b48f-48d3-bb6c-0296de64164e', 'A1', true) values (21, 1, 11, 'A1', true)
, (1, 12, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 'B1', false) , (22, 1, 12, 'B1', false)
; ;
select lives_ok( select lives_ok(
$$ select edit_campsite('87452b88-b48f-48d3-bb6c-0296de64164e', 13, 'C1', false) $$, $$ select edit_campsite(21, 13, 'C1', false) $$,
'Should be able to edit the first campsite.' 'Should be able to edit the first campsite.'
); );
select lives_ok( select lives_ok(
$$ select edit_campsite('9b6370f7-f941-46f2-bc6e-de455675bd0a', 12, 'B2', true) $$, $$ select edit_campsite(22, 12, 'B2', true) $$,
'Should be able to edit the second campsite.' 'Should be able to edit the second campsite.'
); );
select bag_eq( select bag_eq(
$$ select slug::text, campsite_type_id, label, active from campsite $$, $$ select campsite_id, campsite_type_id, label, active from campsite $$,
$$ values ('87452b88-b48f-48d3-bb6c-0296de64164e', 13, 'C1', false) $$ values (21, 13, 'C1', false)
, ('9b6370f7-f941-46f2-bc6e-de455675bd0a', 12, 'B2', true) , (22, 12, 'B2', true)
$$, $$,
'Should have updated all campsites.' 'Should have updated all campsites.'
); );

View File

@ -4,9 +4,8 @@ begin;
select campsite_id select campsite_id
, company_id , company_id
, slug
, campsite_type_id
, label , label
, campsite_type_id
, active , active
from camper.campsite from camper.campsite
where false; where false;

View File

@ -2,6 +2,6 @@
begin; begin;
select has_function_privilege('camper.edit_campsite(uuid, integer, text, boolean)', 'execute'); select has_function_privilege('camper.edit_campsite(integer, integer, text, boolean)', 'execute');
rollback; rollback;

View File

@ -89,3 +89,11 @@ a.missing-translation {
.media-picker footer { .media-picker footer {
bottom: -1em; bottom: -1em;
} }
#campsite-map .guest-only {
display: none;
}
#campsite-map a:hover {
fill: #ffeeaa;
}

View File

@ -135,6 +135,22 @@ function camperUploadForm(el) {
}); });
} }
export function setupCampsiteMap(map) {
if (!map) {
return;
}
for (const lodge of Array.from(map.querySelectorAll('.lodge'))) {
const label = lodge.getAttribute('camper:lodge');
if (!label) {
continue;
}
const link = document.createElementNS('http://www.w3.org/2000/svg', 'a');
link.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/admin/campsites/' + label);
link.append(...lodge.childNodes);
lodge.appendChild(link);
}
}
htmx.onLoad((target) => { htmx.onLoad((target) => {
if (target.tagName === 'DIALOG') { if (target.tagName === 'DIALOG') {
target.showModal(); target.showModal();

View File

@ -4,7 +4,7 @@
--> -->
{{ define "title" -}} {{ define "title" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite.campsiteForm*/ -}} {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite.campsiteForm*/ -}}
{{ if .Slug}} {{ if .ID }}
{{( pgettext "Edit Campsite" "title" )}} {{( pgettext "Edit Campsite" "title" )}}
{{ else }} {{ else }}
{{( pgettext "New Campsite" "title" )}} {{( pgettext "New Campsite" "title" )}}
@ -15,14 +15,14 @@
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite.campsiteForm*/ -}} {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite.campsiteForm*/ -}}
{{ template "settings-tabs" "campsites" }} {{ template "settings-tabs" "campsites" }}
<form <form
{{ if .Slug }} {{ if .ID }}
data-hx-put="/admin/campsites/{{ .Slug }}" data-hx-put="/admin/campsites/{{ .CurrentLabel }}"
{{ else }} {{ else }}
action="/admin/campsites" method="post" action="/admin/campsites" method="post"
{{ end }} {{ end }}
> >
<h2> <h2>
{{ if .Slug }} {{ if .ID }}
{{( pgettext "Edit Campsite" "title" )}} {{( pgettext "Edit Campsite" "title" )}}
{{ else }} {{ else }}
{{( pgettext "New Campsite" "title" )}} {{( pgettext "New Campsite" "title" )}}
@ -30,7 +30,7 @@
</h2> </h2>
{{ CSRFInput }} {{ CSRFInput }}
<fieldset> <fieldset>
{{ if .Slug }} {{ if .ID }}
{{ with .Active -}} {{ with .Active -}}
<label> <label>
<input type="checkbox" name="{{ .Name }}" {{ if .Checked}}checked{{ end }} <input type="checkbox" name="{{ .Name }}" {{ if .Checked}}checked{{ end }}
@ -48,7 +48,7 @@
<select name="{{ .Name }}" <select name="{{ .Name }}"
required required
{{ template "error-attrs" . }}> {{ template "error-attrs" . }}>
{{ if not $.Slug }} {{ if not $.ID }}
<option value="">{{( gettext "Select campsite type" )}}</option> <option value="">{{( gettext "Select campsite type" )}}</option>
{{ end }} {{ end }}
{{ template "list-options" . }} {{ template "list-options" . }}
@ -67,7 +67,7 @@
</fieldset> </fieldset>
<footer> <footer>
<button type="submit"> <button type="submit">
{{ if .Slug }} {{ if .ID }}
{{( pgettext "Update" "action" )}} {{( pgettext "Update" "action" )}}
{{ else }} {{ else }}
{{( pgettext "Add" "action" )}} {{( pgettext "Add" "action" )}}

View File

@ -11,6 +11,7 @@
{{ template "settings-tabs" "campsites" }} {{ template "settings-tabs" "campsites" }}
<a href="/admin/campsites/new">{{( pgettext "Add Campsite" "action" )}}</a> <a href="/admin/campsites/new">{{( pgettext "Add Campsite" "action" )}}</a>
<h2>{{( pgettext "Campsites" "title" )}}</h2> <h2>{{( pgettext "Campsites" "title" )}}</h2>
{{ template "campsite_map.svg" }}
{{ if .Campsites -}} {{ if .Campsites -}}
<table> <table>
<thead> <thead>
@ -23,7 +24,7 @@
<tbody> <tbody>
{{ range .Campsites -}} {{ range .Campsites -}}
<tr> <tr>
<td><a href="/admin/campsites/{{ .Slug }}">{{ .Label }}</a></td> <td><a href="/admin/campsites/{{ .Label }}">{{ .Label }}</a></td>
<td>{{ .Type }}</td> <td>{{ .Type }}</td>
<td>{{ if .Active }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td> <td>{{ if .Active }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td>
</tr> </tr>
@ -33,4 +34,9 @@
{{ else -}} {{ else -}}
<p>{{( gettext "No campsites added yet." )}}</p> <p>{{( gettext "No campsites added yet." )}}</p>
{{- end }} {{- end }}
<script type="module">
import {setupCampsiteMap} from "/static/camper.js";
setupCampsiteMap(document.getElementById('campsite-map'));
</script>
{{- end }} {{- end }}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 245 KiB