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', '')
;
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;
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>')

View File

@ -8,21 +8,21 @@ begin;
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
campsite_slug uuid;
cid integer;
begin
insert into campsite (company_id, campsite_type_id, label)
select company_id, campsite_type_id, label
from 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"';
end if;
return campsite_slug;
return cid;
end
$$
language plpgsql

View File

@ -12,10 +12,10 @@ set search_path to camper, public;
create table campsite (
campsite_id serial primary key,
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),
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;

View File

@ -7,19 +7,19 @@ begin;
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
set label = edit_campsite.label
set label = edit_campsite.new_label
, campsite_type_id = edit_campsite.campsite_type
, active = edit_campsite.active
where slug = edit_campsite.slug
returning slug;
where campsite_id = edit_campsite.campsite_id
returning campsite_id;
$$
language sql
;
revoke execute on function edit_campsite(uuid, integer, text, boolean) from public;
grant execute on function edit_campsite(uuid, integer, text, boolean) to admin;
revoke execute on function edit_campsite(integer, integer, text, boolean) from public;
grant execute on function edit_campsite(integer, integer, text, boolean) to admin;
commit;

View File

@ -16,7 +16,6 @@ import (
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/locale"
"dev.tandem.ws/tandem/camper/pkg/template"
"dev.tandem.ws/tandem/camper/pkg/uuid"
)
type AdminHandler struct {
@ -55,18 +54,18 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
}
default:
if !uuid.Valid(head) {
http.NotFound(w, r)
return
}
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) {
http.NotFound(w, r)
return
}
panic(err)
}
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
switch head {
case "":
switch r.Method {
case http.MethodGet:
f.MustRender(w, r, user, company)
@ -75,6 +74,9 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
}
default:
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) {
rows, err := conn.Query(ctx, `
select campsite.slug
, campsite.label
select campsite.label
, campsite_type.name
, campsite.active
from campsite
@ -108,7 +109,7 @@ func collectCampsiteEntries(ctx context.Context, company *auth.Company, conn *da
var campsites []*campsiteEntry
for rows.Next() {
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
}
campsites = append(campsites, entry)
@ -118,7 +119,6 @@ func collectCampsiteEntries(ctx context.Context, company *auth.Company, conn *da
}
type campsiteEntry struct {
Slug string
Label string
Type string
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) {
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) {
@ -169,12 +169,13 @@ func editCampsite(w http.ResponseWriter, r *http.Request, user *auth.User, compa
f.MustRender(w, r, user, company)
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)
}
type campsiteForm struct {
Slug string
ID int
CurrentLabel string
Active *form.Checkbox
CampsiteType *form.Select
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 {
f.Slug = slug
row := conn.QueryRow(ctx, "select array[campsite_type_id::text], label, active from campsite where slug = $1", slug)
return row.Scan(&f.CampsiteType.Selected, &f.Label.Val, &f.Active.Checked)
func (f *campsiteForm) FillFromDatabase(ctx context.Context, conn *database.Conn, company *auth.Company, label string) error {
f.CurrentLabel = label
row := conn.QueryRow(ctx, `
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 {

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)
}
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{}) {
mustRenderLayout(w, user, company, adminTemplateFile, data, filename)
}
@ -73,8 +82,12 @@ func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, templ
templates = append(templates, "form.gohtml")
files := make([]string, len(templates))
for i, tmpl := range templates {
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 {
panic(err)
}

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\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"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n"
@ -217,7 +217,7 @@ msgid "New Campsite"
msgstr "Nou allotjament"
#: web/templates/admin/campsite/form.gohtml:38
#: web/templates/admin/campsite/index.gohtml:20
#: web/templates/admin/campsite/index.gohtml:21
msgctxt "campsite"
msgid "Active"
msgstr "Actiu"
@ -238,7 +238,7 @@ msgstr "Etiqueta"
#: web/templates/admin/campsite/index.gohtml:6
#: 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"
msgid "Campsites"
msgstr "Allotjaments"
@ -248,29 +248,29 @@ msgctxt "action"
msgid "Add Campsite"
msgstr "Afegeix allotjament"
#: web/templates/admin/campsite/index.gohtml:18
#: web/templates/admin/campsite/index.gohtml:19
msgctxt "header"
msgid "Label"
msgstr "Etiqueta"
#: web/templates/admin/campsite/index.gohtml:19
#: web/templates/admin/campsite/index.gohtml:20
msgctxt "header"
msgid "Type"
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/season/index.gohtml:32
msgid "Yes"
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/season/index.gohtml:32
msgid "No"
msgstr "No"
#: web/templates/admin/campsite/index.gohtml:34
#: web/templates/admin/campsite/index.gohtml:35
msgid "No campsites added yet."
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:13
#: web/templates/admin/layout.gohtml:67
#: web/templates/admin/layout.gohtml:70
msgctxt "title"
msgid "Campsite Types"
msgstr "Tipus dallotjaments"
@ -366,7 +366,7 @@ msgstr "Color"
#: web/templates/admin/season/index.gohtml:6
#: web/templates/admin/season/index.gohtml:13
#: web/templates/admin/layout.gohtml:73
#: web/templates/admin/layout.gohtml:76
msgctxt "title"
msgid "Seasons"
msgstr "Temporades"
@ -413,7 +413,7 @@ msgid "Login"
msgstr "Entra"
#: web/templates/admin/services/index.gohtml:6
#: web/templates/admin/layout.gohtml:79
#: web/templates/admin/layout.gohtml:82
msgctxt "title"
msgid "Services Page"
msgstr "Pàgina de serveis"
@ -493,7 +493,7 @@ msgstr "Desa els canvis"
#: web/templates/admin/taxDetails.gohtml:6
#: web/templates/admin/taxDetails.gohtml:13
#: web/templates/admin/layout.gohtml:64
#: web/templates/admin/layout.gohtml:67
msgctxt "title"
msgid "Tax Details"
msgstr "Configuració fiscal"
@ -579,12 +579,12 @@ msgctxt "action"
msgid "Logout"
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"
msgid "Home Page"
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:12
msgctxt "title"
@ -619,17 +619,17 @@ msgctxt "action"
msgid "Upload"
msgstr "Puja"
#: web/templates/admin/media/picker.gohtml:44
#: web/templates/admin/media/picker.gohtml:47
msgctxt "title"
msgid "Choose Existing Media"
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
msgid "No media uploaded yet."
msgstr "No sha pujat cap mèdia encara."
#: web/templates/admin/media/picker.gohtml:58
#: web/templates/admin/media/picker.gohtml:61
msgctxt "action"
msgid "Cancel"
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."
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."
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."
msgstr "No podeu deixar letiqueta en blanc."

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\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"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n"
@ -217,7 +217,7 @@ msgid "New Campsite"
msgstr "Nuevo alojamiento"
#: web/templates/admin/campsite/form.gohtml:38
#: web/templates/admin/campsite/index.gohtml:20
#: web/templates/admin/campsite/index.gohtml:21
msgctxt "campsite"
msgid "Active"
msgstr "Activo"
@ -238,7 +238,7 @@ msgstr "Etiqueta"
#: web/templates/admin/campsite/index.gohtml:6
#: 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"
msgid "Campsites"
msgstr "Alojamientos"
@ -248,29 +248,29 @@ msgctxt "action"
msgid "Add Campsite"
msgstr "Añadir alojamiento"
#: web/templates/admin/campsite/index.gohtml:18
#: web/templates/admin/campsite/index.gohtml:19
msgctxt "header"
msgid "Label"
msgstr "Etiqueta"
#: web/templates/admin/campsite/index.gohtml:19
#: web/templates/admin/campsite/index.gohtml:20
msgctxt "header"
msgid "Type"
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/season/index.gohtml:32
msgid "Yes"
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/season/index.gohtml:32
msgid "No"
msgstr "No"
#: web/templates/admin/campsite/index.gohtml:34
#: web/templates/admin/campsite/index.gohtml:35
msgid "No campsites added yet."
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:13
#: web/templates/admin/layout.gohtml:67
#: web/templates/admin/layout.gohtml:70
msgctxt "title"
msgid "Campsite Types"
msgstr "Tipos de alojamientos"
@ -366,7 +366,7 @@ msgstr "Color"
#: web/templates/admin/season/index.gohtml:6
#: web/templates/admin/season/index.gohtml:13
#: web/templates/admin/layout.gohtml:73
#: web/templates/admin/layout.gohtml:76
msgctxt "title"
msgid "Seasons"
msgstr "Temporadas"
@ -413,7 +413,7 @@ msgid "Login"
msgstr "Entrar"
#: web/templates/admin/services/index.gohtml:6
#: web/templates/admin/layout.gohtml:79
#: web/templates/admin/layout.gohtml:82
msgctxt "title"
msgid "Services Page"
msgstr "Página de servicios"
@ -493,7 +493,7 @@ msgstr "Guardar los cambios"
#: web/templates/admin/taxDetails.gohtml:6
#: web/templates/admin/taxDetails.gohtml:13
#: web/templates/admin/layout.gohtml:64
#: web/templates/admin/layout.gohtml:67
msgctxt "title"
msgid "Tax Details"
msgstr "Configuración fiscal"
@ -579,12 +579,12 @@ msgctxt "action"
msgid "Logout"
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"
msgid "Home Page"
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:12
msgctxt "title"
@ -619,17 +619,17 @@ msgctxt "action"
msgid "Upload"
msgstr "Subir"
#: web/templates/admin/media/picker.gohtml:44
#: web/templates/admin/media/picker.gohtml:47
msgctxt "title"
msgid "Choose Existing Media"
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
msgid "No media uploaded yet."
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"
msgid "Cancel"
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."
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."
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."
msgstr "No podéis dejar la etiqueta en blanco."

View File

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

View File

@ -11,7 +11,7 @@ select plan(14);
select has_function('camper', 'add_campsite', array ['integer', 'text']);
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 volatility_is('camper', 'add_campsite', array ['integer', 'text'], 'volatile');
select function_privs_are('camper', 'add_campsite', array ['integer', 'text'], 'guest', array[]::text[]);

View File

@ -5,12 +5,13 @@ reset client_min_messages;
begin;
select plan(58);
select plan(53);
set search_path to camper, public;
select has_table('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', 'employee', array['SELECT']);
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_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 col_is_fk('campsite', '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 has_function('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean']);
select function_lang_is('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean'], 'sql');
select function_returns('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean'], 'uuid');
select isnt_definer('camper', 'edit_campsite', array ['uuid', 'integer', 'text', 'boolean']);
select volatility_is('camper', 'edit_campsite', array ['uuid', '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 ['uuid', '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 ['uuid', 'integer', 'text', 'boolean'], 'authenticator', array[]::text[]);
select has_function('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean']);
select function_lang_is('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean'], 'sql');
select function_returns('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean'], 'integer');
select isnt_definer('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean']);
select volatility_is('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean'], 'volatile');
select function_privs_are('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean'], 'guest', 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 ['integer', 'integer', 'text', 'boolean'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'edit_campsite', array ['integer', 'integer', 'text', 'boolean'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate campsite cascade;
@ -46,25 +46,25 @@ values (11, 1, 3, 'Type A')
, (13, 1, 3, 'Type C')
;
insert into campsite (company_id, campsite_type_id, slug, label, active)
values (1, 11, '87452b88-b48f-48d3-bb6c-0296de64164e', 'A1', true)
, (1, 12, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 'B1', false)
insert into campsite (campsite_id, company_id, campsite_type_id, label, active)
values (21, 1, 11, 'A1', true)
, (22, 1, 12, 'B1', false)
;
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.'
);
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.'
);
select bag_eq(
$$ select slug::text, campsite_type_id, label, active from campsite $$,
$$ values ('87452b88-b48f-48d3-bb6c-0296de64164e', 13, 'C1', false)
, ('9b6370f7-f941-46f2-bc6e-de455675bd0a', 12, 'B2', true)
$$ select campsite_id, campsite_type_id, label, active from campsite $$,
$$ values (21, 13, 'C1', false)
, (22, 12, 'B2', true)
$$,
'Should have updated all campsites.'
);

View File

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

View File

@ -2,6 +2,6 @@
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;

View File

@ -89,3 +89,11 @@ a.missing-translation {
.media-picker footer {
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) => {
if (target.tagName === 'DIALOG') {
target.showModal();

View File

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

View File

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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 245 KiB