diff --git a/README.md b/README.md index 80a255e..e0a5071 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,63 @@ -# Module de gestion de matériels pour Paheko +# Module de gestion de matériels et de réservations pour Paheko +Le module original permet de gérer les matériels de l'association. +La source et la documentation se trouve [ici](https://gitea.zaclys.com/lesanges/equipment). +Merci à l'auteur pour ce superbe module ! -Ce module permet de gérer les matériels de l'association: stock, -entrées, sorties, classés par catégorie. - -Un matériel peut entrer dans l'association : -- temporairement (location, emprunt, ...) -- définitivement (achat, ...) - -Dans les deux cas, il est possible de lui associer une écriture de la -compta ou un fichier. - -Un matériel appartenant à l'association peut sortir : -- temporairement (prêt, ...) : il est possible de lui associer un lieu - de stockage ainsi qu'un membre dépositaire du matériel et une date - de retour ; il peut ensuite revenir dans l'association. -- définitivement (vente, ...) : il est possible de lui associer un - membre bénéficiaire du matériel. - -Il est possible de suivre les matériels prêtés ou loués et de savoir -si la date de retour est proche ou dépassée. - -Un matériel présent temporairement dans l'association peut être -retourné à son propriétaire. - -Un matériel dont il n'existe plus d'exemplaire en stock peut être archivé. - -On peut lister l'ensemble des mouvements des matériels ou seulement -ceux d'un matériel donné. - -Il est possible de créer des catégories de matériels, des types -d'entrées et sorties, des lieux de stockage. +Ce **module** permet de gérer en plus des réservations. +Les changements proposés par le module original sont répercutés fréquemment dans cette version. +Dans la description du module sous Paheko, vous trouverez la version du module original et la version de ce module. +[Télécharger ici](https://git.kaz.bzh/bountz/equipment_resa) ## Réservations de matériel -Il est possible de réserver le matériel depuis le site web en mode anonyme -ou depuis l'interface des membres. Les réservations doivent être validées par un administrateur. +Réservation de matériel depuis le site web en mode anonyme ou depuis l'interface des membres. +Les réservations doivent être validées par un administrateur. + +## Fonctionnalités ++ Réservation "_anonyme_" depuis le site web (rattachée à un membre par l'admin) ++ Réservation par les membres connectés ++ Convention de prêt/location ++ Gestion des réservations par l'admin (acceptation, refus,...) ++ Calendrier des réservations ++ Ajout de champs personnalisés sur le matériel (ex: tarif de location) + +### Roadmap ++ Utiliser le module agenda associatif à la place du JS "fullcalendar" + +## Installation + +### Module principal +Télécharger le module depuis le GIT et le décompresser dans un répertoire nommé "modules". +Décompresser le ZIP dans le répertoire "modules" et déplacer le ZIP. +A ce stade, vous devez avoir une arborescence comme suit : modules --> equipment_resa --> [fichiers] +Compresser le répertoire "modules" en le nommant equipment_resa.zip par exemple. +Aller dans l'interface d'admin Paheko, sous Configuration --> Extensions et tout en bas, cliquer sur le bouton **|importer un module| ** +Une fois le module chargé dans Paheko, activer le, puis aller dans sa configuration et cliquer sur l'onglet [Réservation]. +Activer la réservation en cochant la case : Activer les réservations +Si vous avez le module d'origine de _Gestion des Matériels_, vous pouvez importer les données : Cliquer sur le bouton **|importer|** en bas. + +### Installation des pages de réservation sur le site Web. +Les fichiers suivants sont présents dans le code du module réservation sous le répertoire "_web_exemple" pour information/exemple. +Cette partie est exclue du support puisqu'elle touche l'implémentation de la fonctionnalité sur votre site. +Dans le code du module "Site web", importer les fichiers avec les noms suivants : +- equipment.html +- equipment_no_image.png +- equipment_styles.css +- global.js + +Modifier ensuite les fichiers existants suivants : + +- config.html (complet + modif) +- contact.html (complet + modif) +- _head.html (ne contient que la partie à ajouter) +- _breadcrumbs.html (complet + modif) + +### Configuration +Site Web : (Configuration du site web) : Cocher "Accès aux réservations" + +## Dépendances +Site Web : (Configuration du site web) : Cocher "Formulaire de contact" ## Droits d'accès -Le module est accessible uniquement pour les membres ayant au moins le -droit d'écriture en gestion des membres. \ No newline at end of file +Admin : Le module est accessible uniquement pour les membres ayant au moins le droit d'écriture en gestion des membres. +Membres connectés : Accès à la liste du matériel, au détail d'un matériel et à sa réservation. +Visiteurs (Web) : Accès à la liste du matériel, au détail d'un matériel et à sa réservation. \ No newline at end of file diff --git a/_web_exemple/_breadcrumbs.html b/_web_exemple/_breadcrumbs.html new file mode 100644 index 0000000..49c0b06 --- /dev/null +++ b/_web_exemple/_breadcrumbs.html @@ -0,0 +1,20 @@ +{{if !$parent && !$custom}} + {{:error message="Missing parent|custom parameters"}} +{{/if}} + + \ No newline at end of file diff --git a/_web_exemple/_head.html b/_web_exemple/_head.html new file mode 100644 index 0000000..eddf6fd --- /dev/null +++ b/_web_exemple/_head.html @@ -0,0 +1,7 @@ +{{* Bloc à positionner dans la partie de la page *}} + {{if $module.config.show_equipment !== false}} + {{* Include JS et CSS pour le calendrier des réservations *}} + {{:include file="/equipment/_web_calendar.html" title=$page.title|remove_leading_number}} + + + {{/if}} diff --git a/_web_exemple/config.html b/_web_exemple/config.html new file mode 100644 index 0000000..0917522 --- /dev/null +++ b/_web_exemple/config.html @@ -0,0 +1,179 @@ +{{:admin_header title="Configuration du site web"}} + +{{#form on="save"}} + {{:assign uri=null}} + {{if $_POST.home_sticky_uri|trim}} + {{:assign uri=$_POST.home_sticky_uri|trim|regexp_replace:'!^.*?([a-zA-Z0-9_-]+)$!':"$1"}} + {{#pages uri=$uri}} + {{else}} + {{:error message="Cette page n'existe pas ou est en brouillon : %s"|args:$uri}} + {{/pages}} + {{/if}} + + {{:assign var="checkboxes.show_search" value=null}} + {{:assign var="checkboxes.show_hours" value=null}} + {{:assign var="checkboxes.show_bookings" value=null}} + {{:assign var="checkboxes.show_address" value=null}} + {{:assign var="checkboxes.show_email" value=null}} + {{:assign var="checkboxes.show_phone" value=null}} + {{:assign var="checkboxes.show_marker" value=null}} + {{:assign var="checkboxes.contact_form" value=null}} + {{:assign var="checkboxes.show_login" value=null}} + {{:assign var="checkboxes.show_equipment" value=null}} + + {{#foreach from=$checkboxes key="name"}} + {{:assign var="present" from="_POST.%s_present"|args:$name}} + {{:assign var="value" from="_POST.%s"|args:$name}} + {{if $present}} + {{:assign var="checkboxes.%s"|args:$name value=$value|boolval}} + {{/if}} + {{/foreach}} + + {{:save key="config" + color1=null + color2=null + hue1=$_POST.hue1|intval + hue2=$_POST.hue2|intval + layout=$_POST.layout|trim|or:null + background=$_POST.background|trim|or:null + show_search=$checkboxes.show_search + show_hours=$checkboxes.show_hours + show_bookings=$checkboxes.show_bookings + show_address=$checkboxes.show_address + show_email=$checkboxes.show_email + show_phone=$checkboxes.show_phone + show_marker=$checkboxes.show_marker + contact_form=$checkboxes.contact_form + show_login=$checkboxes.show_login + show_equipment=$checkboxes.show_equipment + home_sticky_uri=$uri|or:null + }} + {{:redirect to="?ok=1"}} +{{/form}} + +{{if $_GET.ok}} +

Configuration enregistrée.

+{{/if}} + +{{:form_errors}} + +
+ +{{:assign var="layouts" + 2col="Classique — 2 colonnes" + 1col="Classique — 1 colonne" + wide="Large — 2 colonnes" +}} + +{{:assign var="backgrounds" + gradient="Dégradé entre les deux couleurs" + white_gradient="Dégradé couleur primaire — blanc" + white="Blanc et couleur primaire" + gray="Blanc et gris" + all_white="Tout blanc" + dark="Sombre" +}} + + + +{{if $config.site_disabled}} +

Le site est actuellement désactivé.

+{{else}} +
+ +
+{{/if}} + +{{if $module.config.hue1 === null}} + {{:assign color1=$module.config.color1|or:$config.color1|or:"#999999"|css_hex_extract_hsv}} + {{:assign color2=$module.config.color2|or:$config.color2|or:"#333333"|css_hex_extract_hsv}} + {{:assign var="module.config.hue1" from="color1.h"}} + {{:assign var="module.config.hue2" from="color2.h"}} +{{/if}} + + +
+ Configuration du site web +
+ {{:input type="select" name="layout" label="Disposition sur grand écran" required=true default="2col" options=$layouts help="Sur petit écran (mobile), la disposition reste sur une colonne" source=$module.config}} + {{:input type="hue" name="hue1" label="Couleur primaire" required=false source=$module.config}} + {{:input type="hue" name="hue2" label="Couleur secondaire" required=false source=$module.config}} + {{:input type="select" name="background" options=$backgrounds label="Couleur de fond" required=true source=$module.config}} +
Éléments à afficher sur le site
+ {{:input type="checkbox" name="show_login" value=1 label="Lien de connexion" source=$module.config default=1 help="Si coché, affiche un lien pour se connecter à la gestion de l'association."}} + {{:input type="checkbox" name="show_equipment" value=1 label="Voir le matériel disponible" source=$module.config default=0}} + {{:input type="checkbox" name="show_search" value=1 label="Formulaire de recherche" source=$module.config default=1}} + {{#module name="openings"}} + {{:input type="checkbox" name="show_hours" value=1 label="Horaires d'ouverture" source=$module.config default=1}} + {{/module}} + {{#module name="bookings"}} + {{:input type="checkbox" name="show_bookings" value=1 label="Accès aux réservations" source=$module.config default=1}} + {{/module}} + {{if $config.org_full_address}} + {{:input type="checkbox" name="show_address" value=1 label="Adresse physique" source=$module.config default=1}} + {{:input type="checkbox" name="show_marker" value=1 label="Lien pour ouvrir l'adresse sur une carte" source=$module.config default=1}} + {{/if}} + {{if $config.org_phone}} + {{:input type="checkbox" name="show_phone" value=1 label="Numéro de téléphone" source=$module.config default=1}} + {{/if}} + {{if $config.org_email}} + {{:input type="checkbox" name="show_email" value=1 label="Adresse e-mail" source=$module.config default=1}} + {{:input type="checkbox" name="contact_form" value=1 label="Formulaire de contact" source=$module.config default=0}} + {{/if}} + {{:input type="text" name="home_sticky_uri" label="Identifiant unique de la page à épingler en haut de la page d'accueil" source=$module.config help="En renseignant ici l'identifiant d'une page du site web, celle-ci restera toujours en premier sur la page d'accueil du site. C'est utile par exemple pour afficher une courte présentation de l'association en permanence."}} +
+
+ +

+ {{:button type="submit" name="save" label="Enregistrer" shape="right" class="main"}} +

+ + + +
+ +{{:admin_footer}} \ No newline at end of file diff --git a/_web_exemple/contact.html b/_web_exemple/contact.html new file mode 100644 index 0000000..7f127fa --- /dev/null +++ b/_web_exemple/contact.html @@ -0,0 +1,70 @@ +{{if !$module.config.contact_form || !$config.org_email}} + {{:redirect to="./"}} +{{/if}} +{{if $logged_user.id }} + {{:assign user=$logged_user.nom email=$logged_user.email}} +{{/if}} +{{:assign var=sujets a="Question générale" b="Proposition de matériel" c="Problème sur le site" d="Réservation de matériel" e="Vélo" f="Autre"}} +{{* Si demande de réservation de matériel NON propriété de l'association *}} +{{if $_GET.fonc|trim === 'resa'}} + {{:assign var=sujet value="d"}} + {{* Charge le matériel *}} + {{#load module="equipment" key=$_GET.key assign="equipment"}} + {{:assign var=message value="Bonjour, je souhaite me renseigner pour la réservation de %s du __/__/__ au __/__/__ ."|args:$name}} + {{/load}} +{{elseif $_GET.fonc|trim === 'velo'}} + {{:assign var=sujet value="e"}} + {{#select id, modele FROM plugin_stock_velos WHERE id = :id; :id=$_GET.key }} + {{:assign var=message value="Bonjour, je suis intéressé.e par le vélo %s n° %s ."|args:$modele:$id}} + {{/select}} +{{/if}} +{{#form on="send"}} + {{:captcha verify=true}} + {{if $_POST.name|trim === ''}} + {{:error message="Merci de renseigner un nom."}} + {{elseif $_POST.name|regexp_match:'/https?:|www\./'}} + {{:error message="Le nom ne peut pas comporter d'adresse web"}} + {{elseif $_POST.email|regexp_match:'/https?:|www\./'}} + {{:error message="L'adresse e-mail ne peut pas comporter d'adresse web"}} + {{elseif $_POST.message|trim === ''}} + {{:error message="Merci de renseigner un message."}} + {{elseif !$_POST.email|trim === ''}} + {{:error message="Merci d'indiquer votre adresse e-mail."}} + {{elseif !$_POST.email|check_email}} + {{:error message="L'adresse e-mail renseignée est invalide."}} + {{/if}} + {{if $_POST.subject|trim === ''}} + {{:error message="Merci de choisir un sujet."}} + {{else}} + {{:assign var="sujet" from="sujets.%s"|args:$_POST.subject}} + {{/if}} + {{:assign body="Message de :\n%s\n%s\n\n---\n\n%s"|args:$_POST.name:$_POST.email:$_POST.message}} + {{:mail to=$config.org_email subject=$sujet|trim body=$body replyto=$_POST.email|trim}} + {{:redirect to="%s?sent=ok"|args:$request_url}} +{{/form}} + +{{:include file="./_head.html" title="Contact"}} + +
+

Contactez nous

+ {{if $_GET.sent}} +

Votre message a bien été transmis !

+ {{else}} +
+
+ Renseignez ce formulaire pour nous envoyer un message + {{:form_errors}} +
+ {{:input type="text" name="name" label="Votre nom" default=$user required=true}} + {{:input type="email" name="email" label="Votre adresse e-mail" default=$email required=true}} + {{:input type="select" name="subject" label="Sujet du message" options=$sujets default=$sujet required=true}} + {{:input type="textarea" cols=40 rows=10 name="message" label="Message" default=$message required=true}} +
{{:captcha html=true}}
+
+

{{:button type="submit" name="send" label="Envoyer"}}

+
+
+ {{/if}} +
+ +{{:include file="./_foot.html"}} \ No newline at end of file diff --git a/_web_exemple/equipment.html b/_web_exemple/equipment.html new file mode 100644 index 0000000..2ddb3ac --- /dev/null +++ b/_web_exemple/equipment.html @@ -0,0 +1,55 @@ +{{* -*- brindille -*- *}} +{{:assign var="page.title" value="Matériel à disposition"}} +{{:assign var="page.path" value="./equipment.html"}} + +{{if $_GET.fonc=="save_resa"}} + {{** No header allowed to download the PDF *}} + {{*:assign var=page.title value="Matériel réservé ! Voici votre convention de prêt/location."*}} +{{else}} + {{:include file="./_head.html" title=$page.title|remove_leading_number}} + {{:assign var="custom." _title="🧰 Matériel" _url=$page.path}} + {{if !$_GET.fonc || $_GET.fonc=="inv2" || $_GET.fonc=="inv3"}} + {{:assign var="page" title="Matériel à disposition" path="./equipment.html"}} + {{elseif $_GET.fonc=="voir"}} + {{:assign var="page" title="Détails du matériel" path="./equipment.html"}} + {{:assign var="custom." _title="🔎 Détails" _url="#"}} + {{elseif $_GET.fonc=="resa" || $_GET.fonc=="confirm_resa"}} + {{:assign var="page" title="Réservation du matériel" path="./equipment.html"}} + {{:assign var="custom." _title="🗓️ Réservation" _url="#"}} + {{/if}} + {{:include file="./_breadcrumbs.html"}} {{* custom au lieu de parent car pas dans la base SQL *}} +{{/if}} +

{{$page.title|remove_leading_number}}

+

Le matériel proposé ici provient soit de dons/achats et appartient à l'association soit de prêts ou mis à disposition pour la communauté.
+Dans le cas de matériel mis à disposition, celui-ci n'appartient pas à l'association : cette dernière va simplement vous mettre en relation avec la personne à qui il appartient. +

+ +{{if $_GET.ok}} + {{if $_GET.fonc == "confirm_resa" && !$_GET.rid }} +

Réservation effectuée

+ {{/if}} +{{/if}} + +{{if !$_GET.fonc}} + {{:include file="/equipment/_web_inventory.html" parent=$page.path}} +{{*elseif $_GET.fonc=="inv2"}} + {{:include file="/equipment/_web_inventory2.html" parent=$page.path*}} +{{elseif $_GET.fonc=="inv3"}} + {{:include file="/equipment/_web_inventory3.html" parent=$page.path}} +{{elseif $_GET.fonc=="voir"}} + {{:include file="/equipment/_web_view.html" parent=$page.path}} +{{elseif $_GET.fonc=="resa"}} + {{:include file="/equipment/_web_resa.html" parent=$page.path}} +{{elseif $_GET.fonc=="resadel"}} + {{:include file="/equipment/_web_resadel.html" keep="_GET.keys" parent=$page.path}} +{{elseif $_GET.fonc=="confirm_resa"}} + {{if $_GET.ok}} + {{:include file="/equipment/_web_contract.html" parent=$page.path}} + {{/if}} +{{elseif $_GET.fonc=="save_resa"}} + {{:include file="/equipment/_web_saved_resa.html" parent=$page.path}} +{{else}} + {{:include file="/equipment/_web_inventory.html" parent=$page.path keep="cat_key,prop"}} +{{/if}} + +{{:include file="./_foot.html"}} \ No newline at end of file diff --git a/_web_exemple/equipment_no_image.png b/_web_exemple/equipment_no_image.png new file mode 100644 index 0000000..570bb26 Binary files /dev/null and b/_web_exemple/equipment_no_image.png differ diff --git a/_web_exemple/equipment_styles.css b/_web_exemple/equipment_styles.css new file mode 100644 index 0000000..4ec04e7 --- /dev/null +++ b/_web_exemple/equipment_styles.css @@ -0,0 +1,377 @@ +:root { + --gMainColor: 0, 99, 155; + --gSecondColor: 7, 220, 98; + --gTextColor: 0, 0, 0; +} + +.informations { + margin-top : 0.5em; +} + +.informations dt::after { + content: ' :'; +} + +.informations dl.describe { + border: 0px; + padding: 0.5em; +} + +.informations dl.describe > dt { + float: left; + clear: left; + width: 200px; + text-align: left; + font-weight: bold; + color: green; +} + +.informations dl.describe > dd { + margin: 0 0 0 110px; + padding: 0 0 0.5em 0;; +} + +.nombre { + text-align: right !important; +} + +.resaactionbutton { + z-index: 1; + position: absolute; + top: 15%; + right: 05%; +} + +/* Image style leboncoin */ +figure.leboncoin { + border: thin silver solid; + display: flex; + flex-flow: column; + padding: 5px; + max-width: 170px; + height: 261px; + margin: auto; +} + +img.leboncoin { + max-width: 150px; + max-height: 150px; +} + +figcaption.leboncoin { + background-color: #dce7dc; + color: white; + font: italic smaller sans-serif; + padding: 3px; + text-align: center; + height: 179px; +} + +/** Image vignette */ + +/* Styles par défaut (mobile first) */ +.imagemateriel { + display: block; + object-fit: cover; + width: 100%; /* L'image prendra 100% de la largeur de son conteneur */ + height: auto; /* Maintient le ratio */ + max-width: 250px; + max-height: 150px; +} +/* Pour les écrans de plus de 768px (tablettes/ordinateurs) */ +@media (min-width: 768px) { + .imagemateriel { + display: block; + object-fit: cover; + width: 100%; /* L'image prendra 100% de la largeur de son conteneur */ + height: auto; /* Maintient le ratio */ + max-width: 670px; + max-height: 150px; + } +} + +/* Datepicker */ +dialog.datepicker { + user-select: none; + position: absolute; + left: 0; + padding: .3rem; + border: none; + box-shadow: 0 0 5px #000; + border-radius: .5rem; + z-index: 1000; +} + +/* Bouton Link */ +nav.tabs aside { + float: right; + max-width: 50%; + text-align: right; + clear: right; + margin-top: .5em; +} + +.main.icn-btn { + padding: 13px 20px; + display: inline-block; +} +.icn-btn { + appearance: none; + backface-visibility: hidden; + background-color: rgba(255, 255, 255, 0.7); + border-radius: 5px; + border-style: none; + box-shadow: rgb(0, 148, 133) 0 4px 9px; + box-sizing: border-box; + color: #4cb130; + cursor: pointer; + font-family: Inter,-apple-system,system-ui,"Segoe UI",Helvetica,Arial,sans-serif; + font-size: 16px; + font-weight: 600; + letter-spacing: normal; + line-height: 1.5; + outline: none; + overflow: hidden; + position: relative; + text-align: center; + text-decoration: none; + transform: translate3d(0, 0, 0); + transition: all .3s; + user-select: none; + -webkit-user-select: none; + touch-action: manipulation; + vertical-align: top; + white-space: nowrap; + + a:visited { + color: black; + } +} + +.icn-btn:hover { + background-color: #1e8449; + opacity: 1; + transform: translateY(0); + transition-duration: .35s; +} + +.icn-btn:active { + transform: translateY(2px); + transition-duration: .35s; +} + +.icn-btn:hover { + color: #dfadad; + box-shadow: rgba(39, 174, 96, .2) 0 6px 12px; +} + +/* Datepicker button (.main[data-icon]:after, [data-icon].right:after) */ +.datepicker.icn-btn[data-icon]:before { + display: inline-block; + font-family: "paheko", sans-serif; + text-shadow: 1px 1px 1px var(--gLightBorderColor); + font-size: 1.2em; + line-height: .8em; + vertical-align: middle; + content: attr(data-icon); + font-weight: normal; + text-decoration: none; +} + + +/** Table.list */ + +.list{width:100%;border-collapse:collapse} +.list td,.list th {padding:12px 15px;border:1px solid #ddd;text-align:center;font-size:16px} +.list th {background-color:rgba(var(--gSecondColor), 0.5); color:rgb(var(--gTextColor));} +.list tbody tr:nth-child(even) {background-color:rgba(var(--gSecondColor), 0.2);} +@media (max-width:768px){ + .list thead {display:none} + .list,.list tbody,.list tr, .list td {display:block;width:100%} + .list tr {margin-bottom:15px} + .list td {padding-left:50%;text-align:left; position:relative} + .list td::before{content:attr(data-label); position:absolute;left:0;width:50%;padding-left:15px;font-size:15px;font-weight:bold;text-align:left} +} + +/** Table Pagination */ +.pagination { + clear: both; + list-style-type: none; + padding: 0.4em 0; + text-align: center; +} + +.pagination li { + display: inline-block; + margin: 0 0.3em; +} + +.pagination li.current { + font-size: 1.3em; +} + +.pagination li a { + color: rgb(var(--gTextColor)); +} + +.pagination li button { + color: rgb(var(--gTextColor)); + background: transparent; + border: none; + padding: 0; + margin: 0; + box-shadow: none; + text-decoration: underline; +} + +/** Calendar */ + +/* Base mobile styles (no media query needed) */ +#calendar { + display: none; /* Utiliser !important peut être nécessaire ici */ +} +#calendar_light { + display: block; +} + +/* Tablet and up */ +@media screen and (min-width: 768px) { + #calendar { + display: block; + margin: 0; + padding: 0; + font-family: Arial, Helvetica Neue, Helvetica, sans-serif; + font-size: 14px; + max-width: 800px; + margin: 10px auto; + } + + #calendar_light { + display: none; + } +} +/* Desktop and up +@media screen and (min-width: 1024px) { + +}*/ + +/** Dropdown NEW */ +/** + * Dropdown, eg. interactive