Implement UI enhancements for participant import without transaction in MSIN-4482. Add new styles for import dropzone and integrate import functionality into event management panel. Update navigation for improved access to import features. Increment version code.
This commit is contained in:
@ -11,26 +11,26 @@ Doc de cadrage — source de vérité pour le développement de ce chantier.
|
|||||||
|
|
||||||
## Intention
|
## Intention
|
||||||
|
|
||||||
Utiliser la suite MS1 (gestion, ChronoTrack, hub allégé, signature de dossier, accès par tâche) pour des clients **sans** tunnel d’inscription / paiement en ligne MS1.
|
Utiliser la suite MS1 (gestion, ChronoTrack, outils promoteur existants) pour des clients **sans** tunnel d’inscription / paiement en ligne MS1.
|
||||||
Les participants arrivent par **import fichier** et/ou **saisie rapide** ; ensuite sync ChronoTrack.
|
Les participants arrivent surtout par **import fichier** (puis saisie rapide plus tard) ; ensuite sync ChronoTrack.
|
||||||
|
|
||||||
Aussi utile le **jour J** sur une course **avec** inscription en ligne : ajout / correction rapide (sexe, âge, etc.) **sans** transaction financière.
|
Aussi utile plus tard le **jour J** sur une course **avec** inscription en ligne : correction / ajout rapide **sans** transaction financière.
|
||||||
|
|
||||||
## Décisions actées
|
## Décisions actées
|
||||||
|
|
||||||
| Sujet | Décision |
|
| Sujet | Décision |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Source terrain | Toujours **`resultats_*`** (`resultats_participants`, `resultats_epreuves_commandees`, `resultats_questions`) — pas le panier |
|
| Source terrain | Toujours **`resultats_*`** — **pas de panier / `no_panier`** pour ces lignes |
|
||||||
| Type événement | Nouveau type / mode « sans inscription en ligne » (ex. timing) — à créer |
|
| Type événement | Nouveau type / mode « sans inscription en ligne » (ex. timing) — à créer |
|
||||||
| Paiement MS1 | Hors scope pour ces lignes ; **interdit** le « panier à 0 $ » |
|
| Paiement MS1 | Hors scope ; **interdit** le « panier à 0 $ » |
|
||||||
| Clé unique | ID du fichier client = **`external_id` ChronoTrack** au push (pas seulement `par_id_original`) ; même fichier = fallback import CT |
|
| Clé unique | ID du fichier client = **`external_id` ChronoTrack** au push ; même fichier = fallback import CT |
|
||||||
| Questions import | **Option C** : matcher les questions existantes ; non matchées → preview + **création confirmée** |
|
| Questions import | **Option C** : matcher ; non matchées → preview + **création confirmée** |
|
||||||
| Hub | **Mini-hub / hub allégé** (pas zéro hub) ; grossit avec les options |
|
| Hub promoteur | **Ne pas toucher pour l’instant** — hub actuel tel quel |
|
||||||
| Opérateur | MS1 (Stéphane / équipe) = promoteur opérateur ; délégation = **kits limités par tâche**, pas kit promoteur client « total » |
|
| Qui est promoteur | Opérateur MS1 se met **lui-même** promoteur ; droits clients = kits **minimisés via l’existant** |
|
||||||
| Signature de dossier | **Dans le scope hub** — à cartographier (module exact) ; **≠ waiver** |
|
| Mini-hub | **Plus tard**, au fur et à mesure — pas un livrable immédiat |
|
||||||
| Waiver | **Hors scope** — ne pas assimiler « signature de dossier » au waiver |
|
| Waiver | **Hors scope** |
|
||||||
| Édition fiches | Create/edit sur `resultats_participants` (+ questions) ; **même** chemin jour J sur événement en ligne |
|
| Édition / saisie rapide | Après l’import (phase suivante) |
|
||||||
| Finances | Marqueur d’**origine** obligatoire ; import / saisie rapide **exclus** des rapports paiement / facture |
|
| Finances | Marqueur d’**origine** ; import / saisie rapide exclus des rapports paiement |
|
||||||
|
|
||||||
## Origines participant (marqueur)
|
## Origines participant (marqueur)
|
||||||
|
|
||||||
@ -38,36 +38,60 @@ Aussi utile le **jour J** sur une course **avec** inscription en ligne : ajout /
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `inscription_en_ligne` | Flux payant actuel |
|
| `inscription_en_ligne` | Flux payant actuel |
|
||||||
| `import_fichier` | Bulk modèle fichier |
|
| `import_fichier` | Bulk modèle fichier |
|
||||||
| `saisie_rapide` | Création unitaire / jour J |
|
| `saisie_rapide` | Création unitaire / jour J (plus tard) |
|
||||||
|
|
||||||
## Ancrages code existants
|
## Phases (réalignées)
|
||||||
|
|
||||||
- Sync CT : `php/chronotrack_api/` — push MS1→CT ; aujourd’hui `external_id` ≈ `par_id_original` → **à adapter** si clé import présente
|
1. **UI import superadm** (en cours) — dropzone, mapping colonnes + épreuves, templates persistants
|
||||||
- `ct_entry_id` : ID interne CT post-sync (MSIN-4328) — distinct de la clé fichier
|
2. **Écriture `resultats_*`** — upsert clé unique + questions C (sans panier)
|
||||||
- Questions → CT : `entry_note_*` (MSIN-4461) via `resultats_questions` + `inscriptions_questions`
|
3. **ChronoTrack** — push avec `par_external_id`
|
||||||
- Hub / accès : `php/inc_fx_promoteur_hub.php`, `inscriptions_eve_acces*`
|
4. **Saisie / édition** — fiche rapide (+ jour J)
|
||||||
- Pas d’import générique participants aujourd’hui (seulement FNQ/TQ/PayPal spécialisés)
|
5. **Hub minimisé** — seulement si/quand nécessaire
|
||||||
|
|
||||||
## Hors scope
|
## UI import (décisions)
|
||||||
|
|
||||||
- Waiver / `waiver_cleared` comme livrable de ce projet
|
- Emplacement v1 : **superadm fiche événement** (panneau sous ChronoTrack ; ancre « Import »)
|
||||||
- Kit promoteur client complet sur ce type d’événement
|
- Colonne fichier pour l’épreuve → match vers `epr_id` de l’événement
|
||||||
- Facturation / remboursements pour lignes import / saisie rapide
|
- UX : dropzone ludique + mapping guidé
|
||||||
- Import inverse ChronoTrack → MS1 comme chemin nominal
|
- Template mapping : table `inscriptions_import_templates` (empreinte en-têtes + eve/com)
|
||||||
|
|
||||||
## Phases
|
## Exemple fichier client (référence)
|
||||||
|
|
||||||
0. **Cadrage** — contrat fichier + unique ID ; stratégie questions C ; carto « signature de dossier » ; inventaire `no_panier` / finances
|
Fichier type : export ~Échappée (en-têtes déjà normalisés ; l’original client peut différer).
|
||||||
1. **Fondations** — type événement ; colonnes clé externe + origine ; coquille `pec` sans commande payante
|
|
||||||
2. **Import** — UI + upsert clé unique + questions (match / création confirmée) → `resultats_*`
|
|
||||||
3. **Saisie / édition** — fiche rapide ; ouvert aussi sur courses en ligne (jour J)
|
|
||||||
4. **CT + hub** — sync avec clé fichier ; mini-hub ; accès par tâche ; signature de dossier
|
|
||||||
5. **Ops** — tests mélange payant vs hors-transaction ; filet même fichier → CT
|
|
||||||
|
|
||||||
## Règles agent (pendant le chantier)
|
| Colonne exemple | Cible MS1 |
|
||||||
|
|---|---|
|
||||||
|
| Attendee # | `par_external_id` (clé unique / CT) |
|
||||||
|
| First Name / Last Name | `par_prenom` / `par_nom` |
|
||||||
|
| Ticket Type | `epr` (valeurs → épreuves MS1) |
|
||||||
|
| No dossard | `no_bib` |
|
||||||
|
| Gender (Male/Female/Other) | `par_sexe` (normaliser à l’écriture) |
|
||||||
|
| Age / Birth Date | `par_age` / `par_naissance` |
|
||||||
|
| Nationalité, urgence, allergies, condition médicale, langue… | questions (option C) |
|
||||||
|
|
||||||
- Changement minimal ; référencer **MSIN-4482** dans SQL / commentaires PHP.
|
~1800 lignes, ~10–12 valeurs distinctes de Ticket Type (distances / jours).
|
||||||
- Ne pas brancher ces flux sur le panier / paiement.
|
|
||||||
- Ne pas réintroduire le waiver « pour faire la signature de dossier ».
|
## Fichiers
|
||||||
- Préférer étendre gestion / hub existants plutôt que doubler les fiches participant.
|
|
||||||
- SQL : uniquement **dev préprod**, puis Navicat + sync outil (workflow projet).
|
| Rôle | Fichier |
|
||||||
|
|---|---|
|
||||||
|
| SQL | `sql/MSIN-4482-import-resultats-templates.sql` |
|
||||||
|
| Logique + UI | `php/inc_fx_import_resultats.php` |
|
||||||
|
| AJAX | `superadm/ajax_import_resultats.php` |
|
||||||
|
| Branche fiche eve | `superadm/php/inc_form.php` |
|
||||||
|
| CSS | `superadm/css/style_blue.css` |
|
||||||
|
|
||||||
|
## Hors scope (maintenant)
|
||||||
|
|
||||||
|
- Hub promoteur / mini-hub
|
||||||
|
- Waiver
|
||||||
|
- Écriture `resultats_*` (prochaine étape après UI)
|
||||||
|
- Import inverse CT → MS1
|
||||||
|
|
||||||
|
## Règles agent
|
||||||
|
|
||||||
|
- Changement minimal ; référencer **MSIN-4482**.
|
||||||
|
- **Pas de panier** pour ces participants — écrire dans `resultats_*`.
|
||||||
|
- **Ne pas modifier le hub promoteur** tant que ce n’est pas redemandé.
|
||||||
|
- Ne pas inventer de waiver pour « signature de dossier ».
|
||||||
|
- SQL : **dev préprod** seulement, puis Navicat + sync outil.
|
||||||
|
|||||||
1083
php/inc_fx_import_resultats.php
Normal file
1083
php/inc_fx_import_resultats.php
Normal file
File diff suppressed because it is too large
Load Diff
63
sql/MSIN-4482-import-resultats-templates.sql
Normal file
63
sql/MSIN-4482-import-resultats-templates.sql
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
-- MSIN-4482 — Templates mapping import participants (hors transaction)
|
||||||
|
-- Contexte : UI import fiche événement superadm ; upsert sur clé fichier
|
||||||
|
-- Notes : exécution UNIQUEMENT sur dev préprod ; autres env = Navicat structure + sync_static_db
|
||||||
|
|
||||||
|
SET @db := DATABASE();
|
||||||
|
|
||||||
|
-- Templates de mapping colonnes (réutilisables par empreinte d'en-têtes)
|
||||||
|
CREATE TABLE IF NOT EXISTS `inscriptions_import_templates` (
|
||||||
|
`tpl_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`eve_id` int(10) unsigned NOT NULL DEFAULT 0,
|
||||||
|
`com_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'MSIN-4482 — 0 = lié événement ; >0 = réutilisable côté client',
|
||||||
|
`tpl_name` varchar(120) NOT NULL DEFAULT '',
|
||||||
|
`tpl_header_sig` char(40) NOT NULL DEFAULT '' COMMENT 'sha1 en-têtes normalisés',
|
||||||
|
`tpl_headers_json` mediumtext NOT NULL,
|
||||||
|
`tpl_mapping_json` mediumtext NOT NULL COMMENT 'colonnes + valeurs épreuve + questions',
|
||||||
|
`tpl_actif` tinyint(1) unsigned NOT NULL DEFAULT 1,
|
||||||
|
`tpl_usa_id` int(10) unsigned NOT NULL DEFAULT 0,
|
||||||
|
`tpl_creation` datetime NOT NULL,
|
||||||
|
`tpl_maj` datetime NOT NULL,
|
||||||
|
PRIMARY KEY (`tpl_id`),
|
||||||
|
KEY `idx_import_tpl_eve_sig` (`eve_id`, `tpl_header_sig`),
|
||||||
|
KEY `idx_import_tpl_com_sig` (`com_id`, `tpl_header_sig`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
-- Clé unique fichier (= external_id ChronoTrack) + origine d'entrée
|
||||||
|
SET @col_ext := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'resultats_participants' AND COLUMN_NAME = 'par_external_id'
|
||||||
|
);
|
||||||
|
SET @sql_ext := IF(
|
||||||
|
@col_ext = 0,
|
||||||
|
'ALTER TABLE resultats_participants ADD COLUMN par_external_id VARCHAR(64) NULL DEFAULT NULL COMMENT ''MSIN-4482 — clé unique import / CT external_id'' AFTER par_id_original',
|
||||||
|
'SELECT ''par_external_id already exists'' AS note'
|
||||||
|
);
|
||||||
|
PREPARE stmt_ext FROM @sql_ext;
|
||||||
|
EXECUTE stmt_ext;
|
||||||
|
DEALLOCATE PREPARE stmt_ext;
|
||||||
|
|
||||||
|
SET @col_ori := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'resultats_participants' AND COLUMN_NAME = 'par_origine'
|
||||||
|
);
|
||||||
|
SET @sql_ori := IF(
|
||||||
|
@col_ori = 0,
|
||||||
|
'ALTER TABLE resultats_participants ADD COLUMN par_origine VARCHAR(32) NULL DEFAULT NULL COMMENT ''MSIN-4482 — import_fichier|saisie_rapide|inscription_en_ligne'' AFTER par_external_id',
|
||||||
|
'SELECT ''par_origine already exists'' AS note'
|
||||||
|
);
|
||||||
|
PREPARE stmt_ori FROM @sql_ori;
|
||||||
|
EXECUTE stmt_ori;
|
||||||
|
DEALLOCATE PREPARE stmt_ori;
|
||||||
|
|
||||||
|
SET @idx_ext := (
|
||||||
|
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||||
|
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'resultats_participants' AND INDEX_NAME = 'idx_par_external_eve'
|
||||||
|
);
|
||||||
|
SET @sql_idx := IF(
|
||||||
|
@idx_ext = 0,
|
||||||
|
'ALTER TABLE resultats_participants ADD KEY idx_par_external_eve (eve_id, par_external_id)',
|
||||||
|
'SELECT ''idx_par_external_eve already exists'' AS note'
|
||||||
|
);
|
||||||
|
PREPARE stmt_idx FROM @sql_idx;
|
||||||
|
EXECUTE stmt_idx;
|
||||||
|
DEALLOCATE PREPARE stmt_idx;
|
||||||
70
superadm/ajax_import_resultats.php
Normal file
70
superadm/ajax_import_resultats.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MSIN-4482 — AJAX import participants (hors transaction) — superadm.
|
||||||
|
*/
|
||||||
|
require_once dirname(__FILE__) . '/php/inc_functions.php';
|
||||||
|
require_once dirname(__FILE__) . '/../php/inc_fx_import_resultats.php';
|
||||||
|
|
||||||
|
$intUsaId = fxSuperadmAjaxAuthOrJsonExit();
|
||||||
|
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||||
|
session_write_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$strAction = trim((string)($_REQUEST['a'] ?? ''));
|
||||||
|
$intEveId = intval($_REQUEST['eve_id'] ?? 0);
|
||||||
|
$tabRetour = array('state' => 'error', 'message' => 'action_invalide');
|
||||||
|
|
||||||
|
switch ($strAction) {
|
||||||
|
case 'upload':
|
||||||
|
if ($intEveId <= 0) {
|
||||||
|
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (empty($_FILES['file'])) {
|
||||||
|
$tabRetour = array('state' => 'error', 'message' => 'Fichier manquant');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$tabRetour = fxImportResultatsSaveUpload($intEveId, $_FILES['file']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'save_template':
|
||||||
|
if ($intEveId <= 0) {
|
||||||
|
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$strToken = trim((string)($_REQUEST['token'] ?? ''));
|
||||||
|
$strName = trim((string)($_REQUEST['tpl_name'] ?? ''));
|
||||||
|
$arrMapping = $_REQUEST['mapping'] ?? array();
|
||||||
|
if (is_string($arrMapping)) {
|
||||||
|
$arrDecoded = json_decode($arrMapping, true);
|
||||||
|
$arrMapping = is_array($arrDecoded) ? $arrDecoded : array();
|
||||||
|
}
|
||||||
|
if (!is_array($arrMapping)) {
|
||||||
|
$arrMapping = array();
|
||||||
|
}
|
||||||
|
$tabRetour = fxImportResultatsSaveTemplate($intEveId, $strToken, $strName, $arrMapping, $intUsaId, true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'validate':
|
||||||
|
if ($intEveId <= 0) {
|
||||||
|
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$strToken = trim((string)($_REQUEST['token'] ?? ''));
|
||||||
|
$arrMapping = $_REQUEST['mapping'] ?? array();
|
||||||
|
if (is_string($arrMapping)) {
|
||||||
|
$arrDecoded = json_decode($arrMapping, true);
|
||||||
|
$arrMapping = is_array($arrDecoded) ? $arrDecoded : array();
|
||||||
|
}
|
||||||
|
if (!is_array($arrMapping)) {
|
||||||
|
$arrMapping = array();
|
||||||
|
}
|
||||||
|
$tabRetour = fxImportResultatsValidateMapping($intEveId, $strToken, $arrMapping);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$tabRetour = array('state' => 'error', 'message' => 'action_invalide');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($tabRetour, JSON_UNESCAPED_UNICODE);
|
||||||
@ -396,4 +396,76 @@ table.rapport tr {background-color: #190d7394;}*/
|
|||||||
.superadm-v2-panel__body .custom-control-label {
|
.superadm-v2-panel__body .custom-control-label {
|
||||||
color: #1e293b;
|
color: #1e293b;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MSIN-4482 — Import participants (hors transaction) */
|
||||||
|
.ms1-import__drop {
|
||||||
|
align-items: center;
|
||||||
|
background: linear-gradient(160deg, #f0f9fc 0%, #e4f3f8 100%);
|
||||||
|
border: 2px dashed #045a6e;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #045a6e;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 9.5rem;
|
||||||
|
padding: 1.25rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
.ms1-import__drop:hover,
|
||||||
|
.ms1-import__drop:focus {
|
||||||
|
background: #e8f6fb;
|
||||||
|
box-shadow: 0 4px 14px rgba(4, 90, 110, 0.12);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.ms1-import__drop.is-drag {
|
||||||
|
background: #dff3f9;
|
||||||
|
border-color: #0284a8;
|
||||||
|
border-style: solid;
|
||||||
|
box-shadow: 0 0 0 3px rgba(2, 132, 168, 0.2);
|
||||||
|
transform: scale(1.01);
|
||||||
|
}
|
||||||
|
.ms1-import__drop.is-busy {
|
||||||
|
cursor: wait;
|
||||||
|
opacity: 0.72;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.ms1-import__drop.is-ready {
|
||||||
|
border-style: solid;
|
||||||
|
min-height: 4.5rem;
|
||||||
|
padding: 0.65rem;
|
||||||
|
}
|
||||||
|
.ms1-import__drop.is-ready .ms1-import__drop-sub {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.ms1-import__drop-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.ms1-import__drop-title {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.ms1-import__drop-sub {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
.ms1-import__h {
|
||||||
|
color: #045a6e;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
}
|
||||||
|
.ms1-import__status.alert {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
.ms1-import__epr,
|
||||||
|
.ms1-import__que {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.75rem 0.85rem;
|
||||||
}
|
}
|
||||||
@ -128,6 +128,10 @@ if ($strAction == 'mod') {
|
|||||||
));
|
));
|
||||||
fxChronotrackApiAdminRenderEventPage($intEveIdForm, $strLangue, true, true);
|
fxChronotrackApiAdminRenderEventPage($intEveIdForm, $strLangue, true, true);
|
||||||
fxSuperadmV2PanelClose(array('collapse_id' => 'chronotrackApiCollapse'));
|
fxSuperadmV2PanelClose(array('collapse_id' => 'chronotrackApiCollapse'));
|
||||||
|
|
||||||
|
// MSIN-4482 — Import participants hors transaction (UI + templates)
|
||||||
|
require_once(dirname(__FILE__) . '/../../php/inc_fx_import_resultats.php');
|
||||||
|
fxImportResultatsRenderEventPanel($intEveIdForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -153,6 +157,10 @@ if ($strAction == 'mod') {
|
|||||||
onclick="if (window.jQuery) { jQuery('#chronotrackApiCollapse').collapse('show'); }">
|
onclick="if (window.jQuery) { jQuery('#chronotrackApiCollapse').collapse('show'); }">
|
||||||
ChronoTrack
|
ChronoTrack
|
||||||
</a>
|
</a>
|
||||||
|
<a class="btn btn-secondary" href="#import_resultats"
|
||||||
|
onclick="if (window.jQuery) { jQuery('#importResultatsCollapse').collapse('show'); }">
|
||||||
|
Import
|
||||||
|
</a>
|
||||||
<a class="btn btn-secondary" href="#list_eve_extra">
|
<a class="btn btn-secondary" href="#list_eve_extra">
|
||||||
Champs supplémentaires
|
Champs supplémentaires
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user