Compare commits
No commits in common. "be53b7e5e0cf24f4adebbbdce6a7a98922017b3a" and "371c029e8aade6127392d7dd89238ed6e11d151c" have entirely different histories.
be53b7e5e0
...
371c029e8a
28 changed files with 10 additions and 6950 deletions
|
|
@ -1,44 +0,0 @@
|
|||
<!--
|
||||
Modele de PR Wakdo (Forgejo). Conventions BYAN : Merise Agile + TDD + 64 mantras.
|
||||
Remplis les sections, coche ce qui s'applique, supprime ce qui ne sert pas.
|
||||
-->
|
||||
|
||||
## Description
|
||||
|
||||
<!-- Quoi et pourquoi. Lier la decision / le journal / l'issue si pertinent. -->
|
||||
|
||||
## Type
|
||||
|
||||
- [ ] feat
|
||||
- [ ] fix
|
||||
- [ ] docs
|
||||
- [ ] refactor
|
||||
- [ ] test
|
||||
- [ ] chore
|
||||
|
||||
## Checklist conventions BYAN
|
||||
|
||||
- [ ] Pas d'emoji dans le code, les commits ou les specs (mantra IA-23)
|
||||
- [ ] Commits au format `type: description` en anglais, sans trailer `Co-Authored-By`
|
||||
- [ ] Claims techniques sources si applicable (protocole fact-check)
|
||||
- [ ] Docs Merise / dictionnaire a jour si le modele de donnees change
|
||||
- [ ] Tests ajoutes et passants si du code est touche (unit > integration > e2e)
|
||||
|
||||
## Checklist securite (security-by-design)
|
||||
|
||||
<!-- Cocher ce qui s'applique ; voir SECURITY.md et PROJECT_CONTEXT section 19. -->
|
||||
|
||||
- [ ] Aucun secret commite (CI gitleaks verte) ; `.env` reste gitignore
|
||||
- [ ] Entrees utilisateur validees ; requetes SQL en prepared statements (anti-injection)
|
||||
- [ ] Mots de passe / PIN en argon2id ; pas de donnee sensible en clair ni dans les logs
|
||||
- [ ] Sorties HTML echappees (anti-XSS) ; CSRF gere sur les formulaires d'etat
|
||||
- [ ] Permissions RBAC verifiees cote serveur pour toute action sensible
|
||||
- [ ] Impact RGPD evalue si nouvelles donnees personnelles (retention, droit a l'effacement)
|
||||
|
||||
## Bloc RNCP impacte
|
||||
|
||||
<!-- ex : Bloc 2 Cr 3.b (modelisation), Bloc 1 (accessibilite), Bloc 5 (infra/CI)... -->
|
||||
|
||||
## Base de la PR
|
||||
|
||||
- [ ] La base de cette PR est `dev` (et non `main`)
|
||||
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
Before Width: | Height: | Size: 777 B |
|
|
@ -1,232 +0,0 @@
|
|||
/* Wakdo Admin — Vanilla JS
|
||||
* No framework dependency. Handles:
|
||||
* - User dropdown toggle (topbar)
|
||||
* - Action menu (kebab) open/close
|
||||
* - Sortable table columns (client-side)
|
||||
* - Inline table search
|
||||
* - Tab switching (catalogue)
|
||||
* - Clock display (cuisine)
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/* ---- Utility ---- */
|
||||
function qs(selector, root) {
|
||||
return (root || document).querySelector(selector);
|
||||
}
|
||||
|
||||
function qsa(selector, root) {
|
||||
return Array.from((root || document).querySelectorAll(selector));
|
||||
}
|
||||
|
||||
/* ---- User dropdown (topbar) ---- */
|
||||
function initUserMenu() {
|
||||
var btn = qs('#userMenuBtn');
|
||||
var menu = qs('#userMenu');
|
||||
if (!btn || !menu) return;
|
||||
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
var isOpen = menu.classList.contains('open');
|
||||
closeAllDropdowns();
|
||||
if (!isOpen) {
|
||||
menu.classList.add('open');
|
||||
btn.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- Action menus (kebab per table row) ---- */
|
||||
function initActionMenus() {
|
||||
qsa('.action-menu-btn').forEach(function (btn) {
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
var dropdown = btn.nextElementSibling;
|
||||
if (!dropdown) return;
|
||||
var isOpen = dropdown.classList.contains('open');
|
||||
closeAllDropdowns();
|
||||
if (!isOpen) {
|
||||
dropdown.classList.add('open');
|
||||
btn.classList.add('open');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function closeAllDropdowns() {
|
||||
qsa('.dropdown-menu.open, .action-menu-dropdown.open').forEach(function (el) {
|
||||
el.classList.remove('open');
|
||||
});
|
||||
qsa('.action-menu-btn.open').forEach(function (el) {
|
||||
el.classList.remove('open');
|
||||
});
|
||||
var userBtn = qs('#userMenuBtn');
|
||||
if (userBtn) userBtn.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
document.addEventListener('click', closeAllDropdowns);
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape') closeAllDropdowns();
|
||||
});
|
||||
|
||||
/* ---- Sortable tables ---- */
|
||||
function initSortableTables() {
|
||||
qsa('table').forEach(function (table) {
|
||||
var headers = qsa('th.sortable', table);
|
||||
if (!headers.length) return;
|
||||
|
||||
headers.forEach(function (th) {
|
||||
th.addEventListener('click', function () {
|
||||
var colIndex = parseInt(th.getAttribute('data-col'), 10);
|
||||
var currentDir = th.getAttribute('data-dir') || 'none';
|
||||
var newDir = currentDir === 'asc' ? 'desc' : 'asc';
|
||||
|
||||
/* reset other headers */
|
||||
headers.forEach(function (h) {
|
||||
h.removeAttribute('data-dir');
|
||||
h.classList.remove('sort-asc', 'sort-desc');
|
||||
});
|
||||
|
||||
th.setAttribute('data-dir', newDir);
|
||||
th.classList.add('sort-' + newDir);
|
||||
|
||||
sortTableByCol(table, colIndex, newDir);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sortTableByCol(table, colIndex, dir) {
|
||||
var tbody = table.querySelector('tbody');
|
||||
if (!tbody) return;
|
||||
var rows = Array.from(tbody.querySelectorAll('tr'));
|
||||
|
||||
rows.sort(function (a, b) {
|
||||
var cellA = getCellText(a, colIndex);
|
||||
var cellB = getCellText(b, colIndex);
|
||||
|
||||
/* detect numeric (strip currency, spaces) */
|
||||
var numA = parseFloat(cellA.replace(/[^0-9,.-]/g, '').replace(',', '.'));
|
||||
var numB = parseFloat(cellB.replace(/[^0-9,.-]/g, '').replace(',', '.'));
|
||||
|
||||
var cmp;
|
||||
if (!isNaN(numA) && !isNaN(numB)) {
|
||||
cmp = numA - numB;
|
||||
} else {
|
||||
cmp = cellA.localeCompare(cellB, 'fr');
|
||||
}
|
||||
|
||||
return dir === 'asc' ? cmp : -cmp;
|
||||
});
|
||||
|
||||
rows.forEach(function (row) {
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function getCellText(row, index) {
|
||||
var cell = row.cells[index];
|
||||
if (!cell) return '';
|
||||
return cell.textContent.trim();
|
||||
}
|
||||
|
||||
/* ---- Inline table search ---- */
|
||||
function initTableSearch() {
|
||||
var searchInputs = [
|
||||
{ inputId: 'orderSearch', tableId: 'ordersTable' },
|
||||
{ inputId: 'productSearch', tableId: 'productTable' },
|
||||
{ inputId: 'cmdSearch', tableId: 'cmdTable' },
|
||||
{ inputId: 'userSearch', tableId: 'userTable' }
|
||||
];
|
||||
|
||||
searchInputs.forEach(function (pair) {
|
||||
var input = qs('#' + pair.inputId);
|
||||
var table = qs('#' + pair.tableId);
|
||||
if (!input || !table) return;
|
||||
|
||||
input.addEventListener('input', function () {
|
||||
var term = input.value.trim().toLowerCase();
|
||||
var rows = qsa('tbody tr', table);
|
||||
rows.forEach(function (row) {
|
||||
var text = row.textContent.toLowerCase();
|
||||
row.style.display = term === '' || text.includes(term) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- Tabs (catalogue) ---- */
|
||||
function initTabs() {
|
||||
var tabDefs = [
|
||||
{ btnId: 'tabCategories', panelId: 'panelCategories' },
|
||||
{ btnId: 'tabProduits', panelId: 'panelProduits' },
|
||||
{ btnId: 'tabMenus', panelId: 'panelMenus' }
|
||||
];
|
||||
|
||||
var btns = tabDefs.map(function (d) { return qs('#' + d.btnId); }).filter(Boolean);
|
||||
var panels = tabDefs.map(function (d) { return qs('#' + d.panelId); }).filter(Boolean);
|
||||
|
||||
if (!btns.length) return;
|
||||
|
||||
btns.forEach(function (btn, i) {
|
||||
btn.addEventListener('click', function () {
|
||||
btns.forEach(function (b) { b.classList.remove('active'); });
|
||||
panels.forEach(function (p) { p.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
if (panels[i]) panels[i].classList.add('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- Kitchen clock ---- */
|
||||
function initKitchenClock() {
|
||||
var clockEl = qs('#kitchenTime');
|
||||
if (!clockEl) return;
|
||||
|
||||
function tick() {
|
||||
var now = new Date();
|
||||
var h = String(now.getHours()).padStart(2, '0');
|
||||
var m = String(now.getMinutes()).padStart(2, '0');
|
||||
var s = String(now.getSeconds()).padStart(2, '0');
|
||||
clockEl.textContent = h + ':' + m + ':' + s;
|
||||
}
|
||||
|
||||
tick();
|
||||
setInterval(tick, 1000);
|
||||
}
|
||||
|
||||
/* ---- Refresh button (visual feedback only — no real fetch) ---- */
|
||||
function initRefreshButtons() {
|
||||
qsa('#refreshBtn, #kitchenRefresh').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var svg = btn.querySelector('svg');
|
||||
if (svg) {
|
||||
svg.style.transition = 'transform 0.6s';
|
||||
svg.style.transform = 'rotate(360deg)';
|
||||
setTimeout(function () {
|
||||
svg.style.transition = '';
|
||||
svg.style.transform = '';
|
||||
}, 650);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- Bootstrap ---- */
|
||||
function init() {
|
||||
initUserMenu();
|
||||
initActionMenus();
|
||||
initSortableTables();
|
||||
initTableSearch();
|
||||
initTabs();
|
||||
initKitchenClock();
|
||||
initRefreshButtons();
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
}());
|
||||
|
|
@ -1,306 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Catalogue — Wakdo Admin</title>
|
||||
<link rel="stylesheet" href="assets/css/admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="admin-layout">
|
||||
|
||||
<!-- Topbar -->
|
||||
<header class="topbar">
|
||||
<div class="topbar-logo">
|
||||
<img src="assets/images/logo.png" alt="Wakdo">
|
||||
<div>
|
||||
<span class="topbar-logo-text">Wakdo</span>
|
||||
<span class="topbar-logo-sub">Administration</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topbar-search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" placeholder="Rechercher un produit, une categorie...">
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<div class="topbar-user">
|
||||
<button class="topbar-user-btn" id="userMenuBtn" type="button">
|
||||
<div class="topbar-user-avatar">CJ</div>
|
||||
<div>
|
||||
<div class="topbar-user-name">Corentin J.</div>
|
||||
<div class="topbar-user-role">Administrateur</div>
|
||||
</div>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
||||
</button>
|
||||
<div class="dropdown-menu" id="userMenu">
|
||||
<a href="#">Mon profil</a>
|
||||
<a href="#">Parametres</a>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button">Se deconnecter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Vue d'ensemble</div>
|
||||
<a href="dashboard.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect></svg>
|
||||
Tableau de bord
|
||||
</a>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Catalogue</div>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub active">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>
|
||||
Categories
|
||||
</a>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path></svg>
|
||||
Produits
|
||||
</a>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"></path><rect x="9" y="3" width="6" height="4" rx="2"></rect></svg>
|
||||
Menus
|
||||
</a>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Operations</div>
|
||||
<a href="commandes.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path><line x1="3" y1="6" x2="21" y2="6"></line><path d="M16 10a4 4 0 0 1-8 0"></path></svg>
|
||||
Commandes
|
||||
</a>
|
||||
<a href="cuisine.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8h1a4 4 0 0 1 0 8h-1"></path><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"></path><line x1="6" y1="1" x2="6" y2="4"></line><line x1="10" y1="1" x2="10" y2="4"></line><line x1="14" y1="1" x2="14" y2="4"></line></svg>
|
||||
Cuisine
|
||||
</a>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Administration</div>
|
||||
<a href="users.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
|
||||
Utilisateurs
|
||||
</a>
|
||||
<a href="#" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
|
||||
Roles
|
||||
</a>
|
||||
<a href="#" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"></path></svg>
|
||||
Parametres
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="content">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Catalogue</h1>
|
||||
<p class="page-subtitle">Categories, produits et menus disponibles sur la borne</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<button class="btn btn-secondary" type="button">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
|
||||
Importer
|
||||
</button>
|
||||
<button class="btn btn-primary" type="button">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||
Nouveau produit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="tabs">
|
||||
<button class="tab-btn" id="tabCategories" type="button">Categories (9)</button>
|
||||
<button class="tab-btn active" id="tabProduits" type="button">Produits (53)</button>
|
||||
<button class="tab-btn" id="tabMenus" type="button">Menus (13)</button>
|
||||
</div>
|
||||
|
||||
<!-- Panel: Produits (default) -->
|
||||
<div id="panelProduits" class="tab-panel active">
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<div class="search-field">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" placeholder="Rechercher un produit..." id="productSearch">
|
||||
</div>
|
||||
<select class="filter-select" id="categoryFilter">
|
||||
<option value="">Toutes les categories</option>
|
||||
<option value="burgers">Burgers</option>
|
||||
<option value="wraps">Wraps</option>
|
||||
<option value="salades">Salades</option>
|
||||
<option value="frites">Frites</option>
|
||||
<option value="boissons">Boissons</option>
|
||||
<option value="sauces">Sauces</option>
|
||||
<option value="desserts">Desserts</option>
|
||||
<option value="encas">Encas</option>
|
||||
</select>
|
||||
<select class="filter-select">
|
||||
<option value="">Tous les statuts</option>
|
||||
<option value="active">Disponible</option>
|
||||
<option value="inactive">Indisponible</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<div class="table-wrapper">
|
||||
<table id="productTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:52px;">Image</th>
|
||||
<th class="sortable" data-col="1">Libelle <span class="sort-icon">↕</span></th>
|
||||
<th class="sortable" data-col="2">Categorie <span class="sort-icon">↕</span></th>
|
||||
<th class="sortable" data-col="3" style="text-align:right;">Prix <span class="sort-icon">↕</span></th>
|
||||
<th>Stock</th>
|
||||
<th style="width:50px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><div class="thumb-placeholder"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg></div></td>
|
||||
<td class="fw-600">Big Mac</td>
|
||||
<td class="muted">Burgers</td>
|
||||
<td class="text-right fw-600">6,00 €</td>
|
||||
<td><span class="pill pill-success">Disponible</span></td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Desactiver</button><div class="divider"></div><button class="danger" type="button">Supprimer</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div class="thumb-placeholder"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg></div></td>
|
||||
<td class="fw-600">Royal Bacon</td>
|
||||
<td class="muted">Burgers</td>
|
||||
<td class="text-right fw-600">5,10 €</td>
|
||||
<td><span class="pill pill-success">Disponible</span></td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Desactiver</button><div class="divider"></div><button class="danger" type="button">Supprimer</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div class="thumb-placeholder"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg></div></td>
|
||||
<td class="fw-600">CBO</td>
|
||||
<td class="muted">Burgers</td>
|
||||
<td class="text-right fw-600">8,90 €</td>
|
||||
<td><span class="pill pill-success">Disponible</span></td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Desactiver</button><div class="divider"></div><button class="danger" type="button">Supprimer</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div class="thumb-placeholder"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg></div></td>
|
||||
<td class="fw-600">MC Crispy</td>
|
||||
<td class="muted">Burgers</td>
|
||||
<td class="text-right fw-600">5,30 €</td>
|
||||
<td><span class="pill pill-danger">Indisponible</span></td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Activer</button><div class="divider"></div><button class="danger" type="button">Supprimer</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div class="thumb-placeholder"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg></div></td>
|
||||
<td class="fw-600">Coca Cola</td>
|
||||
<td class="muted">Boissons</td>
|
||||
<td class="text-right fw-600">1,90 €</td>
|
||||
<td><span class="pill pill-success">Disponible</span></td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Desactiver</button><div class="divider"></div><button class="danger" type="button">Supprimer</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div class="thumb-placeholder"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg></div></td>
|
||||
<td class="fw-600">Moyenne Frite</td>
|
||||
<td class="muted">Frites</td>
|
||||
<td class="text-right fw-600">2,75 €</td>
|
||||
<td><span class="pill pill-success">Disponible</span></td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Desactiver</button><div class="divider"></div><button class="danger" type="button">Supprimer</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div class="thumb-placeholder"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg></div></td>
|
||||
<td class="fw-600">Ketchup</td>
|
||||
<td class="muted">Sauces</td>
|
||||
<td class="text-right fw-600">0,70 €</td>
|
||||
<td><span class="pill pill-success">Disponible</span></td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Desactiver</button><div class="divider"></div><button class="danger" type="button">Supprimer</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><div class="thumb-placeholder"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg></div></td>
|
||||
<td class="fw-600">Nuggets x4</td>
|
||||
<td class="muted">Encas</td>
|
||||
<td class="text-right fw-600">4,20 €</td>
|
||||
<td><span class="pill pill-success">Disponible</span></td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Desactiver</button><div class="divider"></div><button class="danger" type="button">Supprimer</button></div></div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<span class="pagination-info">Affichage de 8 sur 53 produits</span>
|
||||
<div class="pagination-controls">
|
||||
<button class="pagination-btn" disabled type="button">«</button>
|
||||
<button class="pagination-btn active" type="button">1</button>
|
||||
<button class="pagination-btn" type="button">2</button>
|
||||
<button class="pagination-btn" type="button">3</button>
|
||||
<button class="pagination-btn" type="button">4</button>
|
||||
<button class="pagination-btn" type="button">»</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panel: Categories -->
|
||||
<div id="panelCategories" class="tab-panel">
|
||||
<div class="table-container">
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Libelle</th>
|
||||
<th>Produits</th>
|
||||
<th>Ordre affichage</th>
|
||||
<th>Statut</th>
|
||||
<th style="width:50px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td class="fw-600">Menus</td><td class="muted">13</td><td class="muted">1</td><td><span class="pill pill-success">Visible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Masquer</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Burgers</td><td class="muted">13</td><td class="muted">2</td><td><span class="pill pill-success">Visible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Masquer</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Wraps</td><td class="muted">4</td><td class="muted">3</td><td><span class="pill pill-success">Visible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Masquer</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Salades</td><td class="muted">3</td><td class="muted">4</td><td><span class="pill pill-success">Visible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Masquer</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Frites</td><td class="muted">5</td><td class="muted">5</td><td><span class="pill pill-success">Visible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Masquer</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Boissons</td><td class="muted">8</td><td class="muted">6</td><td><span class="pill pill-success">Visible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Masquer</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Desserts</td><td class="muted">9</td><td class="muted">7</td><td><span class="pill pill-success">Visible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Masquer</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Encas</td><td class="muted">4</td><td class="muted">8</td><td><span class="pill pill-success">Visible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Masquer</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Sauces</td><td class="muted">7</td><td class="muted">9</td><td><span class="pill pill-neutral">Masquee</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Afficher</button></div></div></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panel: Menus -->
|
||||
<div id="panelMenus" class="tab-panel">
|
||||
<div class="table-container">
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Libelle</th>
|
||||
<th style="text-align:right;">Prix</th>
|
||||
<th>Contenu</th>
|
||||
<th>Statut</th>
|
||||
<th style="width:50px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td class="fw-600">Menu Le 280</td><td class="text-right fw-600">8,80 €</td><td class="muted text-sm">Burger + Frites + Boisson + Sauce</td><td><span class="pill pill-success">Disponible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Desactiver</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Menu Big Tasty</td><td class="text-right fw-600">10,60 €</td><td class="muted text-sm">Burger + Frites + Boisson + Sauce</td><td><span class="pill pill-success">Disponible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Desactiver</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Menu Big Mac</td><td class="text-right fw-600">8,00 €</td><td class="muted text-sm">Burger + Frites + Boisson + Sauce</td><td><span class="pill pill-success">Disponible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Desactiver</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Menu CBO</td><td class="text-right fw-600">10,90 €</td><td class="muted text-sm">Burger + Frites + Boisson + Sauce</td><td><span class="pill pill-success">Disponible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Desactiver</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Menu Royal Cheese</td><td class="text-right fw-600">6,40 €</td><td class="muted text-sm">Burger + Frites + Boisson + Sauce</td><td><span class="pill pill-success">Disponible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Desactiver</button></div></div></td></tr>
|
||||
<tr><td class="fw-600">Menu Royal Bacon</td><td class="text-right fw-600">7,05 €</td><td class="muted text-sm">Burger + Frites + Boisson + Sauce</td><td><span class="pill pill-danger">Indisponible</span></td><td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Modifier</a><button type="button">Activer</button></div></div></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Commandes — Wakdo Admin</title>
|
||||
<link rel="stylesheet" href="assets/css/admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="admin-layout">
|
||||
|
||||
<!-- Topbar -->
|
||||
<header class="topbar">
|
||||
<div class="topbar-logo">
|
||||
<img src="assets/images/logo.png" alt="Wakdo">
|
||||
<div>
|
||||
<span class="topbar-logo-text">Wakdo</span>
|
||||
<span class="topbar-logo-sub">Administration</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topbar-search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" placeholder="N° commande, statut...">
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<div class="topbar-user">
|
||||
<button class="topbar-user-btn" id="userMenuBtn" type="button">
|
||||
<div class="topbar-user-avatar">CJ</div>
|
||||
<div>
|
||||
<div class="topbar-user-name">Corentin J.</div>
|
||||
<div class="topbar-user-role">Administrateur</div>
|
||||
</div>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
||||
</button>
|
||||
<div class="dropdown-menu" id="userMenu">
|
||||
<a href="#">Mon profil</a>
|
||||
<a href="#">Parametres</a>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button">Se deconnecter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Vue d'ensemble</div>
|
||||
<a href="dashboard.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect></svg>
|
||||
Tableau de bord
|
||||
</a>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Catalogue</div>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>
|
||||
Categories
|
||||
</a>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path></svg>
|
||||
Produits
|
||||
</a>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"></path><rect x="9" y="3" width="6" height="4" rx="2"></rect></svg>
|
||||
Menus
|
||||
</a>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Operations</div>
|
||||
<a href="commandes.html" class="sidebar-item active">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path><line x1="3" y1="6" x2="21" y2="6"></line><path d="M16 10a4 4 0 0 1-8 0"></path></svg>
|
||||
Commandes
|
||||
</a>
|
||||
<a href="cuisine.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8h1a4 4 0 0 1 0 8h-1"></path><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"></path><line x1="6" y1="1" x2="6" y2="4"></line><line x1="10" y1="1" x2="10" y2="4"></line><line x1="14" y1="1" x2="14" y2="4"></line></svg>
|
||||
Cuisine
|
||||
</a>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Administration</div>
|
||||
<a href="users.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
|
||||
Utilisateurs
|
||||
</a>
|
||||
<a href="#" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
|
||||
Roles
|
||||
</a>
|
||||
<a href="#" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"></path></svg>
|
||||
Parametres
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="content">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Commandes</h1>
|
||||
<p class="page-subtitle">Historique et suivi de toutes les commandes</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<div class="search-field">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" placeholder="N° commande..." id="cmdSearch">
|
||||
</div>
|
||||
<select class="filter-select" id="statusFilter">
|
||||
<option value="">Tous les statuts</option>
|
||||
<option value="pending">En attente</option>
|
||||
<option value="paid">Payee</option>
|
||||
<option value="preparing">En preparation</option>
|
||||
<option value="ready">Prete</option>
|
||||
<option value="delivered">Livree</option>
|
||||
<option value="cancelled">Annulee</option>
|
||||
</select>
|
||||
<select class="filter-select">
|
||||
<option value="">Tous les modes</option>
|
||||
<option value="kiosk">Borne</option>
|
||||
<option value="counter">Comptoir</option>
|
||||
<option value="drive">Drive</option>
|
||||
</select>
|
||||
<select class="filter-select">
|
||||
<option value="today">Aujourd'hui</option>
|
||||
<option value="7d">7 derniers jours</option>
|
||||
<option value="30d">30 derniers jours</option>
|
||||
<option value="custom">Personnalise</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<div class="table-wrapper">
|
||||
<table id="cmdTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sortable" data-col="0">N° <span class="sort-icon">↕</span></th>
|
||||
<th class="sortable" data-col="1">Date / Heure <span class="sort-icon">↕</span></th>
|
||||
<th>Mode</th>
|
||||
<th>Source</th>
|
||||
<th>Statut</th>
|
||||
<th style="text-align:center;">Lignes</th>
|
||||
<th class="sortable" data-col="6" style="text-align:right;">Total <span class="sort-icon">↕</span></th>
|
||||
<th style="width:50px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1087</td>
|
||||
<td class="muted">09/05/2026 13:42</td>
|
||||
<td><span class="pill pill-info">Sur place</span></td>
|
||||
<td class="muted text-sm">Borne</td>
|
||||
<td><span class="pill pill-success">Livree</span></td>
|
||||
<td style="text-align:center;" class="muted">3</td>
|
||||
<td class="text-right fw-600">18,70 €</td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Voir detail</a><div class="divider"></div><button class="danger" type="button">Annuler</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1086</td>
|
||||
<td class="muted">09/05/2026 13:38</td>
|
||||
<td><span class="pill pill-neutral">A emporter</span></td>
|
||||
<td class="muted text-sm">Comptoir</td>
|
||||
<td><span class="pill pill-warning">En preparation</span></td>
|
||||
<td style="text-align:center;" class="muted">5</td>
|
||||
<td class="text-right fw-600">24,30 €</td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Voir detail</a><div class="divider"></div><button class="danger" type="button">Annuler</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1085</td>
|
||||
<td class="muted">09/05/2026 13:31</td>
|
||||
<td><span class="pill pill-info">Sur place</span></td>
|
||||
<td class="muted text-sm">Borne</td>
|
||||
<td><span class="pill pill-success">Livree</span></td>
|
||||
<td style="text-align:center;" class="muted">2</td>
|
||||
<td class="text-right fw-600">11,40 €</td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Voir detail</a><div class="divider"></div><button class="danger" type="button">Annuler</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1084</td>
|
||||
<td class="muted">09/05/2026 13:27</td>
|
||||
<td><span class="pill pill-neutral">A emporter</span></td>
|
||||
<td class="muted text-sm">Drive</td>
|
||||
<td><span class="pill pill-success">Livree</span></td>
|
||||
<td style="text-align:center;" class="muted">2</td>
|
||||
<td class="text-right fw-600">8,80 €</td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Voir detail</a><div class="divider"></div><button class="danger" type="button">Annuler</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1083</td>
|
||||
<td class="muted">09/05/2026 13:19</td>
|
||||
<td><span class="pill pill-info">Sur place</span></td>
|
||||
<td class="muted text-sm">Borne</td>
|
||||
<td><span class="pill pill-neutral">Annulee</span></td>
|
||||
<td style="text-align:center;" class="muted">1</td>
|
||||
<td class="text-right muted">6,40 €</td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Voir detail</a></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1082</td>
|
||||
<td class="muted">09/05/2026 13:14</td>
|
||||
<td><span class="pill pill-info">Sur place</span></td>
|
||||
<td class="muted text-sm">Borne</td>
|
||||
<td><span class="pill pill-success">Livree</span></td>
|
||||
<td style="text-align:center;" class="muted">7</td>
|
||||
<td class="text-right fw-600">32,10 €</td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Voir detail</a><div class="divider"></div><button class="danger" type="button">Annuler</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1081</td>
|
||||
<td class="muted">09/05/2026 13:08</td>
|
||||
<td><span class="pill pill-neutral">A emporter</span></td>
|
||||
<td class="muted text-sm">Drive</td>
|
||||
<td><span class="pill pill-success">Livree</span></td>
|
||||
<td style="text-align:center;" class="muted">2</td>
|
||||
<td class="text-right fw-600">10,90 €</td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Voir detail</a><div class="divider"></div><button class="danger" type="button">Annuler</button></div></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1080</td>
|
||||
<td class="muted">09/05/2026 12:58</td>
|
||||
<td><span class="pill pill-info">Sur place</span></td>
|
||||
<td class="muted text-sm">Comptoir</td>
|
||||
<td><span class="pill pill-success">Livree</span></td>
|
||||
<td style="text-align:center;" class="muted">4</td>
|
||||
<td class="text-right fw-600">15,60 €</td>
|
||||
<td><div class="action-menu"><button class="action-menu-btn" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="action-menu-dropdown"><a href="#">Voir detail</a><div class="divider"></div><button class="danger" type="button">Annuler</button></div></div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<span class="pagination-info">Affichage de 8 sur 231 commandes</span>
|
||||
<div class="pagination-controls">
|
||||
<button class="pagination-btn" disabled type="button">«</button>
|
||||
<button class="pagination-btn active" type="button">1</button>
|
||||
<button class="pagination-btn" type="button">2</button>
|
||||
<button class="pagination-btn" type="button">3</button>
|
||||
<span style="padding:0 4px;color:var(--color-text-muted);font-size:12px;">...</span>
|
||||
<button class="pagination-btn" type="button">29</button>
|
||||
<button class="pagination-btn" type="button">»</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,253 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cuisine — Wakdo Admin</title>
|
||||
<link rel="stylesheet" href="assets/css/admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="admin-layout">
|
||||
|
||||
<!-- Topbar -->
|
||||
<header class="topbar">
|
||||
<div class="topbar-logo">
|
||||
<img src="assets/images/logo.png" alt="Wakdo">
|
||||
<div>
|
||||
<span class="topbar-logo-text">Wakdo</span>
|
||||
<span class="topbar-logo-sub">Administration</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topbar-search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" placeholder="Rechercher une commande...">
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<div class="topbar-user">
|
||||
<button class="topbar-user-btn" id="userMenuBtn" type="button">
|
||||
<div class="topbar-user-avatar">CJ</div>
|
||||
<div>
|
||||
<div class="topbar-user-name">Corentin J.</div>
|
||||
<div class="topbar-user-role">Administrateur</div>
|
||||
</div>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
||||
</button>
|
||||
<div class="dropdown-menu" id="userMenu">
|
||||
<a href="#">Mon profil</a>
|
||||
<a href="#">Parametres</a>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button">Se deconnecter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Vue d'ensemble</div>
|
||||
<a href="dashboard.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect></svg>
|
||||
Tableau de bord
|
||||
</a>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Catalogue</div>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>
|
||||
Categories
|
||||
</a>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path></svg>
|
||||
Produits
|
||||
</a>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"></path><rect x="9" y="3" width="6" height="4" rx="2"></rect></svg>
|
||||
Menus
|
||||
</a>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Operations</div>
|
||||
<a href="commandes.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path><line x1="3" y1="6" x2="21" y2="6"></line><path d="M16 10a4 4 0 0 1-8 0"></path></svg>
|
||||
Commandes
|
||||
</a>
|
||||
<a href="cuisine.html" class="sidebar-item active">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8h1a4 4 0 0 1 0 8h-1"></path><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"></path><line x1="6" y1="1" x2="6" y2="4"></line><line x1="10" y1="1" x2="10" y2="4"></line><line x1="14" y1="1" x2="14" y2="4"></line></svg>
|
||||
Cuisine
|
||||
</a>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Administration</div>
|
||||
<a href="users.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
|
||||
Utilisateurs
|
||||
</a>
|
||||
<a href="#" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
|
||||
Roles
|
||||
</a>
|
||||
<a href="#" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"></path></svg>
|
||||
Parametres
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="content">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Cuisine</h1>
|
||||
<p class="page-subtitle">Commandes en attente de preparation — tries par heure croissante</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<span class="text-sm text-muted" id="kitchenTime">13:42:17</span>
|
||||
<button class="btn btn-secondary" type="button" id="kitchenRefresh">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>
|
||||
Actualiser
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kitchen cards grid -->
|
||||
<div class="kitchen-grid" id="kitchenGrid">
|
||||
|
||||
<div class="kitchen-card">
|
||||
<div class="kitchen-card-header">
|
||||
<div>
|
||||
<div class="kitchen-order-num">#1086</div>
|
||||
<div class="kitchen-order-time">13:38 — 4 min</div>
|
||||
</div>
|
||||
<span class="pill pill-neutral">A emporter</span>
|
||||
</div>
|
||||
<div class="kitchen-card-body">
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x2</span>Menu Big Mac</span>
|
||||
</div>
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x1</span>Grande Frite</span>
|
||||
</div>
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x2</span>Coca Cola</span>
|
||||
</div>
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x1</span>Nuggets x4</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kitchen-card-footer">
|
||||
<span class="text-sm text-muted">24,30 €</span>
|
||||
<button class="btn btn-primary btn-sm" type="button">Marquer prete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kitchen-card">
|
||||
<div class="kitchen-card-header">
|
||||
<div>
|
||||
<div class="kitchen-order-num">#1088</div>
|
||||
<div class="kitchen-order-time">13:44 — 2 min</div>
|
||||
</div>
|
||||
<span class="pill pill-info">Sur place</span>
|
||||
</div>
|
||||
<div class="kitchen-card-body">
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x1</span>Menu CBO</span>
|
||||
</div>
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x1</span>Fanta Orange</span>
|
||||
</div>
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x1</span>Classic Barbecue</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kitchen-card-footer">
|
||||
<span class="text-sm text-muted">13,50 €</span>
|
||||
<button class="btn btn-primary btn-sm" type="button">Marquer prete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kitchen-card">
|
||||
<div class="kitchen-card-header">
|
||||
<div>
|
||||
<div class="kitchen-order-num">#1089</div>
|
||||
<div class="kitchen-order-time">13:45 — 1 min</div>
|
||||
</div>
|
||||
<span class="pill pill-neutral">A emporter</span>
|
||||
</div>
|
||||
<div class="kitchen-card-body">
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x3</span>Menu Royal Cheese</span>
|
||||
</div>
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x1</span>Petite Salade</span>
|
||||
</div>
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x3</span>Eau</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kitchen-card-footer">
|
||||
<span class="text-sm text-muted">25,50 €</span>
|
||||
<button class="btn btn-primary btn-sm" type="button">Marquer prete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kitchen-card">
|
||||
<div class="kitchen-card-header">
|
||||
<div>
|
||||
<div class="kitchen-order-num">#1090</div>
|
||||
<div class="kitchen-order-time">13:46 — maintenant</div>
|
||||
</div>
|
||||
<span class="pill pill-info">Sur place</span>
|
||||
</div>
|
||||
<div class="kitchen-card-body">
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x1</span>Big Tasty Bacon</span>
|
||||
</div>
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x1</span>Grande Frite</span>
|
||||
</div>
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x1</span>Ice Tea Peche</span>
|
||||
</div>
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x2</span>Ketchup</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kitchen-card-footer">
|
||||
<span class="text-sm text-muted">12,60 €</span>
|
||||
<button class="btn btn-primary btn-sm" type="button">Marquer prete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kitchen-card">
|
||||
<div class="kitchen-card-header">
|
||||
<div>
|
||||
<div class="kitchen-order-num">#1091</div>
|
||||
<div class="kitchen-order-time">13:46 — maintenant</div>
|
||||
</div>
|
||||
<span class="pill pill-neutral">A emporter</span>
|
||||
</div>
|
||||
<div class="kitchen-card-body">
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x4</span>Cheeseburger</span>
|
||||
</div>
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x2</span>Moyenne Frite</span>
|
||||
</div>
|
||||
<div class="kitchen-line">
|
||||
<span><span class="kitchen-qty">x4</span>Coca Cola</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kitchen-card-footer">
|
||||
<span class="text-sm text-muted">22,10 €</span>
|
||||
<button class="btn btn-primary btn-sm" type="button">Marquer prete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,411 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tableau de bord — Wakdo Admin</title>
|
||||
<link rel="stylesheet" href="assets/css/admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="admin-layout">
|
||||
|
||||
<!-- Topbar -->
|
||||
<header class="topbar">
|
||||
<div class="topbar-logo">
|
||||
<img src="assets/images/logo.png" alt="Wakdo">
|
||||
<div>
|
||||
<span class="topbar-logo-text">Wakdo</span>
|
||||
<span class="topbar-logo-sub">Administration</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="topbar-search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
<input type="text" placeholder="Rechercher une commande, un produit...">
|
||||
</div>
|
||||
|
||||
<div class="topbar-actions">
|
||||
<div class="topbar-user">
|
||||
<button class="topbar-user-btn" id="userMenuBtn" type="button" aria-haspopup="true" aria-expanded="false">
|
||||
<div class="topbar-user-avatar">CJ</div>
|
||||
<div>
|
||||
<div class="topbar-user-name">Corentin J.</div>
|
||||
<div class="topbar-user-role">Administrateur</div>
|
||||
</div>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="dropdown-menu" id="userMenu">
|
||||
<a href="#">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"></circle><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"></path></svg>
|
||||
Mon profil
|
||||
</a>
|
||||
<a href="#">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"></path></svg>
|
||||
Parametres
|
||||
</a>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
|
||||
Se deconnecter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Vue d'ensemble</div>
|
||||
<a href="dashboard.html" class="sidebar-item active">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect></svg>
|
||||
Tableau de bord
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Catalogue</div>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>
|
||||
Categories
|
||||
</a>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path></svg>
|
||||
Produits
|
||||
</a>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"></path><rect x="9" y="3" width="6" height="4" rx="2"></rect></svg>
|
||||
Menus
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Operations</div>
|
||||
<a href="commandes.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path><line x1="3" y1="6" x2="21" y2="6"></line><path d="M16 10a4 4 0 0 1-8 0"></path></svg>
|
||||
Commandes
|
||||
</a>
|
||||
<a href="cuisine.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8h1a4 4 0 0 1 0 8h-1"></path><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"></path><line x1="6" y1="1" x2="6" y2="4"></line><line x1="10" y1="1" x2="10" y2="4"></line><line x1="14" y1="1" x2="14" y2="4"></line></svg>
|
||||
Cuisine
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Administration</div>
|
||||
<a href="users.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
|
||||
Utilisateurs
|
||||
</a>
|
||||
<a href="#" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
|
||||
Roles
|
||||
</a>
|
||||
<a href="#" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"></path></svg>
|
||||
Parametres
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="content">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Tableau de bord</h1>
|
||||
<p class="page-subtitle">Samedi 9 mai 2026 — Service en cours</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<select class="filter-select" id="periodFilter">
|
||||
<option value="today" selected>Aujourd'hui</option>
|
||||
<option value="7d">7 derniers jours</option>
|
||||
<option value="30d">30 derniers jours</option>
|
||||
<option value="custom">Personnalise</option>
|
||||
</select>
|
||||
<button class="btn btn-secondary" type="button" id="refreshBtn">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>
|
||||
Actualiser
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI Cards -->
|
||||
<div class="kpi-grid">
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">Ventes du jour</div>
|
||||
<div class="kpi-value">2 847,50 €</div>
|
||||
<div>
|
||||
<span class="kpi-delta up">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"></polyline></svg>
|
||||
+12,4 %
|
||||
</span>
|
||||
<span class="kpi-delta-sub">vs hier</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">Commandes du jour</div>
|
||||
<div class="kpi-value">231</div>
|
||||
<div>
|
||||
<span class="kpi-delta up">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"></polyline></svg>
|
||||
+8,2 %
|
||||
</span>
|
||||
<span class="kpi-delta-sub">vs hier</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">Panier moyen</div>
|
||||
<div class="kpi-value">12,33 €</div>
|
||||
<div>
|
||||
<span class="kpi-delta down">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
||||
-1,8 %
|
||||
</span>
|
||||
<span class="kpi-delta-sub">vs hier</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">Produits actifs</div>
|
||||
<div class="kpi-value">53</div>
|
||||
<div>
|
||||
<span class="kpi-delta neutral">
|
||||
—
|
||||
</span>
|
||||
<span class="kpi-delta-sub">inchange</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Orders -->
|
||||
<div class="section-block">
|
||||
<div class="card-header">
|
||||
<span class="card-title">Dernieres commandes</span>
|
||||
<a href="commandes.html" class="btn btn-ghost btn-sm">
|
||||
Voir tout
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="toolbar" style="padding: 12px 16px 0; margin-bottom: 0;">
|
||||
<div class="toolbar-left">
|
||||
<div class="search-field">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" placeholder="N° de commande..." id="orderSearch">
|
||||
</div>
|
||||
<select class="filter-select">
|
||||
<option value="">Tous les statuts</option>
|
||||
<option value="pending">En attente</option>
|
||||
<option value="preparing">En preparation</option>
|
||||
<option value="ready">Prete</option>
|
||||
<option value="delivered">Livree</option>
|
||||
<option value="cancelled">Annulee</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table id="ordersTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sortable" data-col="0">N° <span class="sort-icon">↕</span></th>
|
||||
<th class="sortable" data-col="1">Heure <span class="sort-icon">↕</span></th>
|
||||
<th>Mode</th>
|
||||
<th>Statut</th>
|
||||
<th class="sortable" data-col="4">Total <span class="sort-icon">↕</span></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1087</td>
|
||||
<td class="muted">13:42</td>
|
||||
<td><span class="pill pill-info">Sur place</span></td>
|
||||
<td><span class="pill pill-success">Livree</span></td>
|
||||
<td class="fw-600">18,70 €</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button" aria-label="Actions">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
||||
Voir detail
|
||||
</a>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path><path d="M10 11v6"></path><path d="M14 11v6"></path></svg>
|
||||
Annuler
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1086</td>
|
||||
<td class="muted">13:38</td>
|
||||
<td><span class="pill pill-neutral">A emporter</span></td>
|
||||
<td><span class="pill pill-warning">En preparation</span></td>
|
||||
<td class="fw-600">24,30 €</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button" aria-label="Actions">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
||||
Voir detail
|
||||
</a>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path><path d="M10 11v6"></path><path d="M14 11v6"></path></svg>
|
||||
Annuler
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1085</td>
|
||||
<td class="muted">13:31</td>
|
||||
<td><span class="pill pill-info">Sur place</span></td>
|
||||
<td><span class="pill pill-success">Livree</span></td>
|
||||
<td class="fw-600">11,40 €</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button" aria-label="Actions">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>Voir detail</a>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path></svg>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1084</td>
|
||||
<td class="muted">13:27</td>
|
||||
<td><span class="pill pill-neutral">A emporter</span></td>
|
||||
<td><span class="pill pill-success">Livree</span></td>
|
||||
<td class="fw-600">8,80 €</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button" aria-label="Actions">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>Voir detail</a>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path></svg>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1083</td>
|
||||
<td class="muted">13:19</td>
|
||||
<td><span class="pill pill-info">Sur place</span></td>
|
||||
<td><span class="pill pill-neutral">Annulee</span></td>
|
||||
<td class="fw-600 text-muted">6,40 €</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button" aria-label="Actions">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>Voir detail</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1082</td>
|
||||
<td class="muted">13:14</td>
|
||||
<td><span class="pill pill-info">Sur place</span></td>
|
||||
<td><span class="pill pill-success">Livree</span></td>
|
||||
<td class="fw-600">32,10 €</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button" aria-label="Actions">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>Voir detail</a>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path></svg>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1081</td>
|
||||
<td class="muted">13:08</td>
|
||||
<td><span class="pill pill-neutral">A emporter</span></td>
|
||||
<td><span class="pill pill-success">Livree</span></td>
|
||||
<td class="fw-600">10,90 €</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button" aria-label="Actions">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>Voir detail</a>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path></svg>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="mono fw-600">#1080</td>
|
||||
<td class="muted">12:58</td>
|
||||
<td><span class="pill pill-info">Sur place</span></td>
|
||||
<td><span class="pill pill-success">Livree</span></td>
|
||||
<td class="fw-600">15,60 €</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button" aria-label="Actions">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>Voir detail</a>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path></svg>Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination">
|
||||
<span class="pagination-info">Affichage de 8 sur 231 commandes</span>
|
||||
<div class="pagination-controls">
|
||||
<button class="pagination-btn" disabled type="button">«</button>
|
||||
<button class="pagination-btn active" type="button">1</button>
|
||||
<button class="pagination-btn" type="button">2</button>
|
||||
<button class="pagination-btn" type="button">3</button>
|
||||
<span style="padding: 0 4px; color: var(--color-text-muted); font-size: 12px;">...</span>
|
||||
<button class="pagination-btn" type="button">29</button>
|
||||
<button class="pagination-btn" type="button">»</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Connexion — Wakdo Admin</title>
|
||||
<link rel="stylesheet" href="assets/css/admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-page">
|
||||
<div class="login-card">
|
||||
<div class="login-logo">
|
||||
<img src="assets/images/logo.png" alt="Wakdo">
|
||||
<span class="login-logo-title">Wakdo Admin</span>
|
||||
<span class="login-logo-sub">Back-office de gestion</span>
|
||||
</div>
|
||||
|
||||
<form action="dashboard.html" method="get">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="email">Adresse e-mail</label>
|
||||
<input
|
||||
class="form-input"
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="prenom.nom@wakdo.fr"
|
||||
autocomplete="email"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="password">Mot de passe</label>
|
||||
<input
|
||||
class="form-input"
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Votre mot de passe"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" style="width:100%;height:40px;justify-content:center;font-size:14px;font-weight:600;margin-top:4px;">
|
||||
Se connecter
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="login-footer">
|
||||
<a href="#">Mot de passe oublie ?</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,296 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Utilisateurs — Wakdo Admin</title>
|
||||
<link rel="stylesheet" href="assets/css/admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="admin-layout">
|
||||
|
||||
<!-- Topbar -->
|
||||
<header class="topbar">
|
||||
<div class="topbar-logo">
|
||||
<img src="assets/images/logo.png" alt="Wakdo">
|
||||
<div>
|
||||
<span class="topbar-logo-text">Wakdo</span>
|
||||
<span class="topbar-logo-sub">Administration</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topbar-search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" placeholder="Rechercher un utilisateur...">
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<div class="topbar-user">
|
||||
<button class="topbar-user-btn" id="userMenuBtn" type="button">
|
||||
<div class="topbar-user-avatar">CJ</div>
|
||||
<div>
|
||||
<div class="topbar-user-name">Corentin J.</div>
|
||||
<div class="topbar-user-role">Administrateur</div>
|
||||
</div>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
||||
</button>
|
||||
<div class="dropdown-menu" id="userMenu">
|
||||
<a href="#">Mon profil</a>
|
||||
<a href="#">Parametres</a>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button">Se deconnecter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Vue d'ensemble</div>
|
||||
<a href="dashboard.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect></svg>
|
||||
Tableau de bord
|
||||
</a>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Catalogue</div>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>
|
||||
Categories
|
||||
</a>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path></svg>
|
||||
Produits
|
||||
</a>
|
||||
<a href="catalogue.html" class="sidebar-item sidebar-item-sub">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"></path><rect x="9" y="3" width="6" height="4" rx="2"></rect></svg>
|
||||
Menus
|
||||
</a>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Operations</div>
|
||||
<a href="commandes.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path><line x1="3" y1="6" x2="21" y2="6"></line><path d="M16 10a4 4 0 0 1-8 0"></path></svg>
|
||||
Commandes
|
||||
</a>
|
||||
<a href="cuisine.html" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8h1a4 4 0 0 1 0 8h-1"></path><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"></path><line x1="6" y1="1" x2="6" y2="4"></line><line x1="10" y1="1" x2="10" y2="4"></line><line x1="14" y1="1" x2="14" y2="4"></line></svg>
|
||||
Cuisine
|
||||
</a>
|
||||
</div>
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">Administration</div>
|
||||
<a href="users.html" class="sidebar-item active">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
|
||||
Utilisateurs
|
||||
</a>
|
||||
<a href="#" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
|
||||
Roles
|
||||
</a>
|
||||
<a href="#" class="sidebar-item">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"></path></svg>
|
||||
Parametres
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="content">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Utilisateurs</h1>
|
||||
<p class="page-subtitle">Comptes et droits d'acces au back-office</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<button class="btn btn-primary" type="button">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||
Nouvel utilisateur
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<div class="search-field">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
<input type="text" placeholder="Nom, email..." id="userSearch">
|
||||
</div>
|
||||
<select class="filter-select">
|
||||
<option value="">Tous les roles</option>
|
||||
<option value="admin">Administrateur</option>
|
||||
<option value="manager">Manager</option>
|
||||
<option value="preparation">Preparation</option>
|
||||
<option value="accueil">Accueil</option>
|
||||
</select>
|
||||
<select class="filter-select">
|
||||
<option value="">Tous les statuts</option>
|
||||
<option value="active">Actif</option>
|
||||
<option value="inactive">Inactif</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<div class="table-wrapper">
|
||||
<table id="userTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sortable" data-col="0">Nom / Email <span class="sort-icon">↕</span></th>
|
||||
<th class="sortable" data-col="1">Role <span class="sort-icon">↕</span></th>
|
||||
<th>Statut</th>
|
||||
<th class="sortable" data-col="3">Derniere connexion <span class="sort-icon">↕</span></th>
|
||||
<th style="width:50px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-center gap-8">
|
||||
<div class="topbar-user-avatar" style="width:30px;height:30px;font-size:12px;">CJ</div>
|
||||
<div>
|
||||
<div class="fw-600">Corentin Jog</div>
|
||||
<div class="text-sm text-muted">corentin@wakdo.fr</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="pill pill-info">Administrateur</span></td>
|
||||
<td><span class="pill pill-success">Actif</span></td>
|
||||
<td class="muted">09/05/2026 13:42</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#">Modifier</a>
|
||||
<button type="button">Reinitialiser mdp</button>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button">Desactiver</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-center gap-8">
|
||||
<div class="topbar-user-avatar" style="width:30px;height:30px;font-size:12px;background:#E5E7EB;color:#4A4A4A;">ML</div>
|
||||
<div>
|
||||
<div class="fw-600">Marie Laurent</div>
|
||||
<div class="text-sm text-muted">marie.laurent@wakdo.fr</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="pill pill-warning">Manager</span></td>
|
||||
<td><span class="pill pill-success">Actif</span></td>
|
||||
<td class="muted">09/05/2026 10:15</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#">Modifier</a>
|
||||
<button type="button">Reinitialiser mdp</button>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button">Desactiver</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-center gap-8">
|
||||
<div class="topbar-user-avatar" style="width:30px;height:30px;font-size:12px;background:#E5E7EB;color:#4A4A4A;">AD</div>
|
||||
<div>
|
||||
<div class="fw-600">Ahmed Diallo</div>
|
||||
<div class="text-sm text-muted">ahmed.diallo@wakdo.fr</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="pill pill-neutral">Preparation</span></td>
|
||||
<td><span class="pill pill-success">Actif</span></td>
|
||||
<td class="muted">09/05/2026 11:00</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#">Modifier</a>
|
||||
<button type="button">Reinitialiser mdp</button>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button">Desactiver</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-center gap-8">
|
||||
<div class="topbar-user-avatar" style="width:30px;height:30px;font-size:12px;background:#E5E7EB;color:#4A4A4A;">SP</div>
|
||||
<div>
|
||||
<div class="fw-600">Sophie Petit</div>
|
||||
<div class="text-sm text-muted">sophie.petit@wakdo.fr</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="pill pill-neutral">Accueil</span></td>
|
||||
<td><span class="pill pill-success">Actif</span></td>
|
||||
<td class="muted">09/05/2026 09:58</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#">Modifier</a>
|
||||
<button type="button">Reinitialiser mdp</button>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button">Desactiver</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-center gap-8">
|
||||
<div class="topbar-user-avatar" style="width:30px;height:30px;font-size:12px;background:#F3F4F6;color:#9CA3AF;">TM</div>
|
||||
<div>
|
||||
<div class="fw-600" style="color:var(--color-text-muted);">Thomas Martin</div>
|
||||
<div class="text-sm text-muted">thomas.martin@wakdo.fr</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="pill pill-neutral">Preparation</span></td>
|
||||
<td><span class="pill pill-neutral">Inactif</span></td>
|
||||
<td class="muted">02/04/2026 17:30</td>
|
||||
<td>
|
||||
<div class="action-menu">
|
||||
<button class="action-menu-btn" type="button">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
|
||||
</button>
|
||||
<div class="action-menu-dropdown">
|
||||
<a href="#">Modifier</a>
|
||||
<button type="button">Reactiver</button>
|
||||
<div class="divider"></div>
|
||||
<button class="danger" type="button">Supprimer</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<span class="pagination-info">5 utilisateurs</span>
|
||||
<div class="pagination-controls">
|
||||
<button class="pagination-btn active" type="button">1</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="assets/js/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* data.js — Data loading layer for the Wakdo kiosk.
|
||||
*
|
||||
* P5 reads static JSON copies in /data/ (same origin).
|
||||
* In P4, swap the BASE_URL constants to point to REST API endpoints.
|
||||
* The function signatures and return shapes remain unchanged so that
|
||||
* page scripts need no modification when the data source changes.
|
||||
*
|
||||
* Category-to-slug mapping (mirrors data/categories.json id field):
|
||||
* 1=menus 2=boissons 3=burgers 4=frites 5=encas
|
||||
* 6=wraps 7=salades 8=desserts 9=sauces
|
||||
*/
|
||||
|
||||
/* --- P4 swap point -------------------------------------------------------
|
||||
* TODO(P4): replace these two paths with API endpoints, e.g.:
|
||||
* const CATEGORIES_URL = '/api/categories';
|
||||
* const PRODUCTS_URL = '/api/products';
|
||||
* The rest of this file is API-agnostic.
|
||||
* ----------------------------------------------------------------------- */
|
||||
const CATEGORIES_URL = 'data/categories.json';
|
||||
const PRODUCTS_URL = 'data/produits.json';
|
||||
|
||||
/** @type {Array|null} — in-memory cache to avoid repeated fetches */
|
||||
let _categoriesCache = null;
|
||||
|
||||
/** @type {Object|null} */
|
||||
let _productsCache = null;
|
||||
|
||||
/**
|
||||
* Fetches and caches the categories list.
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
export async function loadCategories() {
|
||||
if (_categoriesCache) return _categoriesCache;
|
||||
const res = await fetch(CATEGORIES_URL);
|
||||
if (!res.ok) throw new Error(`Failed to load categories: HTTP ${res.status}`);
|
||||
_categoriesCache = await res.json();
|
||||
return _categoriesCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and caches the full products object keyed by category slug.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export async function loadProducts() {
|
||||
if (_productsCache) return _productsCache;
|
||||
const res = await fetch(PRODUCTS_URL);
|
||||
if (!res.ok) throw new Error(`Failed to load products: HTTP ${res.status}`);
|
||||
_productsCache = await res.json();
|
||||
return _productsCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of products for a given category slug.
|
||||
* Returns [] if the slug is not found.
|
||||
* @param {string} slug — e.g. "burgers", "menus"
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
export async function getProductsByCategory(slug) {
|
||||
const data = await loadProducts();
|
||||
return data[slug] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the category object for the given id.
|
||||
* @param {number} id
|
||||
* @returns {Promise<Object|null>}
|
||||
*/
|
||||
export async function getCategoryById(id) {
|
||||
const cats = await loadCategories();
|
||||
return cats.find(c => c.id === id) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a product by its numeric id, searching all category slates.
|
||||
* Returns null if not found.
|
||||
* @param {number} id
|
||||
* @returns {Promise<Object|null>}
|
||||
*/
|
||||
export async function findProduct(id) {
|
||||
const data = await loadProducts();
|
||||
for (const slug of Object.keys(data)) {
|
||||
const found = data[slug].find(p => p.id === id);
|
||||
if (found) return { ...found, categorie: slug };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a category id integer to its slug string.
|
||||
* Derived from data/categories.json — kept here as a convenience
|
||||
* so page scripts can convert query-string ids without an extra fetch.
|
||||
*/
|
||||
export const CATEGORY_ID_TO_SLUG = {
|
||||
1: 'menus',
|
||||
2: 'boissons',
|
||||
3: 'burgers',
|
||||
4: 'frites',
|
||||
5: 'encas',
|
||||
6: 'wraps',
|
||||
7: 'salades',
|
||||
8: 'desserts',
|
||||
9: 'sauces'
|
||||
};
|
||||
|
||||
/**
|
||||
* Inverse of the above: slug -> id.
|
||||
*/
|
||||
export const CATEGORY_SLUG_TO_ID = Object.fromEntries(
|
||||
Object.entries(CATEGORY_ID_TO_SLUG).map(([id, slug]) => [slug, Number(id)])
|
||||
);
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* nav.js — Shared navigation helpers loaded on every page.
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Inject the mode badge ("Sur place" / "A emporter") into any
|
||||
* element with [data-mode-badge] on the page.
|
||||
* - Sync the cart item count into any element with [data-cart-count].
|
||||
* - Handle the mode query-string on page load (welcome -> categories handoff).
|
||||
*
|
||||
* Import this module in every page that has a header.
|
||||
*/
|
||||
|
||||
import { getMode, setMode, getCartCount } from './state.js';
|
||||
|
||||
/**
|
||||
* Reads ?mode= from the current URL and persists it if present.
|
||||
* Called once on DOMContentLoaded so that the welcome -> categories
|
||||
* navigation stores the chosen mode before any render.
|
||||
*/
|
||||
function syncModeFromURL() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const modeParam = params.get('mode');
|
||||
if (modeParam === 'sur-place' || modeParam === 'a-emporter') {
|
||||
setMode(modeParam);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the human-readable mode label into every [data-mode-badge] element.
|
||||
*/
|
||||
function renderModeBadge() {
|
||||
const mode = getMode();
|
||||
const label = mode === 'a-emporter' ? 'A emporter' : 'Sur place';
|
||||
document.querySelectorAll('[data-mode-badge]').forEach(el => {
|
||||
el.textContent = label;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cart item count badge in every [data-cart-count] element.
|
||||
* Called on load and after any cart mutation.
|
||||
*/
|
||||
export function refreshCartBadge() {
|
||||
const count = getCartCount();
|
||||
document.querySelectorAll('[data-cart-count]').forEach(el => {
|
||||
el.textContent = count > 0 ? String(count) : '';
|
||||
el.hidden = count === 0;
|
||||
});
|
||||
}
|
||||
|
||||
/* Initialise on DOM ready */
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
syncModeFromURL();
|
||||
renderModeBadge();
|
||||
refreshCartBadge();
|
||||
});
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
/*
|
||||
* page-cart.js — Shopping cart screen.
|
||||
*
|
||||
* Displays all cart lines with quantity controls and totals.
|
||||
* Handles two item shapes:
|
||||
* - Simple product: { id, type, libelle, prix_cents, quantite, image }
|
||||
* - Composed menu: { ...above, composition: {...}, supplement_cents: number }
|
||||
*
|
||||
* Menu lines render a composition breakdown beneath the product name.
|
||||
* Simple product lines render as before (no composition block).
|
||||
*
|
||||
* TVA: 10% (taux normal restauration, France 2024 — simplification MVP).
|
||||
* TODO: verify exact applicable TVA rate with an accountant in P3.
|
||||
* The real rate depends on sur-place vs a-emporter, alcohol content, etc.
|
||||
*
|
||||
* The total displayed is TTC (tax inclusive) because French consumer law
|
||||
* requires prices shown to end-consumers to include all taxes.
|
||||
*/
|
||||
|
||||
import { getCart, removeFromCart, updateQuantity, getTotalCents, computeMenuLineCents, clearCart, formatPrice } from './state.js';
|
||||
import { refreshCartBadge } from './nav.js';
|
||||
|
||||
/* TVA rate used for display breakdown only — stored prices are already TTC */
|
||||
const TVA_RATE = 0.10;
|
||||
|
||||
const cartList = document.getElementById('cart-list');
|
||||
const emptyBlock = document.getElementById('cart-empty');
|
||||
const summaryBlock= document.getElementById('cart-summary');
|
||||
const totalTTC = document.getElementById('total-ttc');
|
||||
const totalHT = document.getElementById('total-ht');
|
||||
const totalTVA = document.getElementById('total-tva');
|
||||
const payBtn = document.getElementById('pay-btn');
|
||||
const abandonBtn = document.getElementById('abandon-btn');
|
||||
|
||||
function renderCart() {
|
||||
const items = getCart();
|
||||
refreshCartBadge();
|
||||
|
||||
if (!items.length) {
|
||||
cartList.innerHTML = '';
|
||||
emptyBlock.hidden = false;
|
||||
summaryBlock.hidden = true;
|
||||
if (payBtn) payBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
emptyBlock.hidden = true;
|
||||
summaryBlock.hidden = false;
|
||||
if (payBtn) payBtn.disabled = false;
|
||||
|
||||
cartList.innerHTML = '';
|
||||
items.forEach((item, index) => {
|
||||
const isMenu = item.type === 'menu';
|
||||
const lineTotalCents = isMenu
|
||||
? computeMenuLineCents(item)
|
||||
: item.prix_cents * item.quantite;
|
||||
|
||||
const row = document.createElement('li');
|
||||
row.className = 'cart-line';
|
||||
row.setAttribute('aria-label', `${item.libelle}, quantite ${item.quantite}`);
|
||||
|
||||
row.innerHTML = `
|
||||
<img
|
||||
class="cart-line__image"
|
||||
src="${item.image}"
|
||||
alt="${item.libelle}"
|
||||
onerror="this.src='assets/images/ui/logo.png'; this.alt='Image non disponible';"
|
||||
>
|
||||
<div class="cart-line__info">
|
||||
<span class="cart-line__name">${item.libelle}</span>
|
||||
<span class="cart-line__unit-price">${formatPrice(item.prix_cents)} / unite${isMenu && (item.supplement_cents ?? 0) > 0 ? ` + ${formatPrice(item.supplement_cents)} suppl.` : ''}</span>
|
||||
${isMenu && item.composition ? renderCompositionBlock(item) : ''}
|
||||
</div>
|
||||
<div class="cart-line__qty" role="group" aria-label="Quantite de ${item.libelle}">
|
||||
<button
|
||||
class="qty-btn qty-btn--minus"
|
||||
data-index="${index}"
|
||||
aria-label="Diminuer la quantite de ${item.libelle}"
|
||||
type="button"
|
||||
>-</button>
|
||||
<span class="qty-value" aria-live="polite">${item.quantite}</span>
|
||||
<button
|
||||
class="qty-btn qty-btn--plus"
|
||||
data-index="${index}"
|
||||
aria-label="Augmenter la quantite de ${item.libelle}"
|
||||
type="button"
|
||||
>+</button>
|
||||
</div>
|
||||
<span class="cart-line__total">${formatPrice(lineTotalCents)}</span>
|
||||
<button
|
||||
class="cart-line__remove"
|
||||
data-index="${index}"
|
||||
aria-label="Supprimer ${item.libelle} du panier"
|
||||
type="button"
|
||||
>
|
||||
<img src="assets/images/ui/trash.png" alt="" aria-hidden="true" width="24" height="24">
|
||||
</button>
|
||||
`;
|
||||
cartList.appendChild(row);
|
||||
});
|
||||
|
||||
/* Attach event listeners after render */
|
||||
cartList.querySelectorAll('.qty-btn--minus').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const idx = parseInt(btn.dataset.index, 10);
|
||||
const cart = getCart();
|
||||
updateQuantity(idx, cart[idx].quantite - 1);
|
||||
renderCart();
|
||||
});
|
||||
});
|
||||
|
||||
cartList.querySelectorAll('.qty-btn--plus').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const idx = parseInt(btn.dataset.index, 10);
|
||||
const cart = getCart();
|
||||
updateQuantity(idx, cart[idx].quantite + 1);
|
||||
renderCart();
|
||||
});
|
||||
});
|
||||
|
||||
cartList.querySelectorAll('.cart-line__remove').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const idx = parseInt(btn.dataset.index, 10);
|
||||
removeFromCart(idx);
|
||||
renderCart();
|
||||
});
|
||||
});
|
||||
|
||||
/* Update totals */
|
||||
const ttcCents = getTotalCents();
|
||||
/* Back-calculate HT from TTC (prices assumed to be TTC already) */
|
||||
const htCents = Math.round(ttcCents / (1 + TVA_RATE));
|
||||
const tvaCents = ttcCents - htCents;
|
||||
|
||||
if (totalTTC) totalTTC.textContent = formatPrice(ttcCents);
|
||||
if (totalHT) totalHT.textContent = formatPrice(htCents);
|
||||
if (totalTVA) totalTVA.textContent = formatPrice(tvaCents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the composition breakdown HTML for a menu cart line.
|
||||
* Renders burger (with personalisation options), accompagnement with taille,
|
||||
* boisson with taille, sauce, and the supplement summary if applicable.
|
||||
*
|
||||
* @param {Object} item — cart item with type === 'menu' and composition object
|
||||
* @returns {string} HTML string
|
||||
*/
|
||||
function renderCompositionBlock(item) {
|
||||
const c = item.composition;
|
||||
if (!c) return '';
|
||||
|
||||
const burgerOpts = c.burger.options && c.burger.options.length
|
||||
? ` (${c.burger.options.map(o => o === 'sans-oignon' ? 'sans oignon' : 'avec fromage').join(', ')})`
|
||||
: '';
|
||||
|
||||
const accompTailleLabel = c.accompagnement.taille === 'G' ? ' grande' : ' normale';
|
||||
const boissonTailleLabel = c.boisson.taille === 'G' ? ' grande' : ' normale';
|
||||
|
||||
const nbGrandes = (c.accompagnement.taille === 'G' ? 1 : 0) + (c.boisson.taille === 'G' ? 1 : 0);
|
||||
const supplTotal = item.supplement_cents ?? 0;
|
||||
|
||||
return `
|
||||
<ul class="cart-line__composition" aria-label="Composition du menu">
|
||||
<li class="cart-line__comp-item">+ ${c.burger.libelle}${burgerOpts}</li>
|
||||
<li class="cart-line__comp-item">+ ${c.accompagnement.libelle}${accompTailleLabel}</li>
|
||||
<li class="cart-line__comp-item">+ ${c.boisson.libelle}${boissonTailleLabel}</li>
|
||||
<li class="cart-line__comp-item">+ ${c.sauce.libelle}</li>
|
||||
${supplTotal > 0 ? `<li class="cart-line__comp-suppl">Supplement ${nbGrandes} grande(s) : +${formatPrice(supplTotal)}</li>` : ''}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
if (abandonBtn) {
|
||||
abandonBtn.addEventListener('click', () => {
|
||||
clearCart();
|
||||
window.location.href = 'categories.html';
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', renderCart);
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* page-confirmation.js — Order confirmation screen.
|
||||
*
|
||||
* Generates a short order number: "WK-" + Date.now() encoded in base 36.
|
||||
* This is session-unique and human-readable at the counter.
|
||||
*
|
||||
* Clears the cart on load so that "Nouvelle commande" starts fresh.
|
||||
*/
|
||||
|
||||
import { clearCart, getTotalCents, formatPrice } from './state.js';
|
||||
|
||||
const orderNumberEl = document.getElementById('order-number');
|
||||
const orderTotalEl = document.getElementById('order-total');
|
||||
const newOrderBtn = document.getElementById('new-order-btn');
|
||||
|
||||
function generateOrderNumber() {
|
||||
return 'WK-' + Date.now().toString(36).toUpperCase();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
/* Capture total before clearing */
|
||||
const totalCents = getTotalCents();
|
||||
|
||||
if (orderTotalEl) {
|
||||
orderTotalEl.textContent = formatPrice(totalCents);
|
||||
}
|
||||
|
||||
if (orderNumberEl) {
|
||||
orderNumberEl.textContent = generateOrderNumber();
|
||||
}
|
||||
|
||||
/* Clear cart immediately — order is confirmed */
|
||||
clearCart();
|
||||
});
|
||||
|
||||
if (newOrderBtn) {
|
||||
newOrderBtn.addEventListener('click', () => {
|
||||
/* clearCart() already called on DOMContentLoaded, but guard anyway */
|
||||
clearCart();
|
||||
window.location.href = 'index.html';
|
||||
});
|
||||
}
|
||||
|
|
@ -1,702 +0,0 @@
|
|||
/*
|
||||
* page-product-menu.js — Multi-step menu composer for the Wakdo kiosk.
|
||||
*
|
||||
* Imported by page-product.js only when the loaded product has type === 'menu'.
|
||||
* Keeping the composer in its own module avoids bloating page-product.js and
|
||||
* makes future unit-testing of the composition logic straightforward.
|
||||
*
|
||||
* Steps:
|
||||
* 1 — Burger selection + personalisation options (sans oignon / avec fromage)
|
||||
* 2 — Accompagnement (frites or salades) + taille toggle
|
||||
* 3 — Boisson + taille toggle
|
||||
* 4 — Sauce
|
||||
* 5 — Recap + "Ajouter au panier"
|
||||
*
|
||||
* Price rule: grande taille = +50 centimes per sized item (accompagnement + boisson).
|
||||
*
|
||||
* A11y: role=dialog, aria-modal=true, focus-trap (Tab cycles inside the modal),
|
||||
* ESC closes/cancels, focus is moved to the first interactive element on each step.
|
||||
*/
|
||||
|
||||
import { getProductsByCategory } from './data.js';
|
||||
import { addToCart, computeMenuLineCents, formatPrice } from './state.js';
|
||||
import { refreshCartBadge } from './nav.js';
|
||||
|
||||
const SUPPLEMENT_GRANDE_CENTS = 50;
|
||||
const TOTAL_STEPS = 5;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Public entry-point — called from page-product.js */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Initialises and opens the menu composer modal.
|
||||
* Fetches required category products, builds the initial state, then renders.
|
||||
*
|
||||
* @param {Object} menu — product object with type === 'menu'
|
||||
* @param {string} returnCategory — category slug to redirect to after add/cancel
|
||||
*/
|
||||
export async function openMenuComposer(menu, returnCategory) {
|
||||
let burgers, frites, salades, boissons, sauces;
|
||||
try {
|
||||
[burgers, frites, salades, boissons, sauces] = await Promise.all([
|
||||
getProductsByCategory('burgers'),
|
||||
getProductsByCategory('frites'),
|
||||
getProductsByCategory('salades'),
|
||||
getProductsByCategory('boissons'),
|
||||
getProductsByCategory('sauces')
|
||||
]);
|
||||
} catch (err) {
|
||||
console.error('Menu composer: failed to load category products', err);
|
||||
return;
|
||||
}
|
||||
|
||||
const accompagnements = [...frites, ...salades];
|
||||
|
||||
/* Heuristic pre-selection: if the menu name contains a burger name, pre-select it.
|
||||
* "Menu CBO" -> first burger whose nom equals "CBO".
|
||||
* Fallback: first burger in the list. */
|
||||
const menuNameUpper = menu.nom.toUpperCase();
|
||||
const preselectedBurger =
|
||||
burgers.find(b => menuNameUpper.includes(b.nom.toUpperCase())) ?? burgers[0] ?? null;
|
||||
|
||||
/* Composer internal state — single mutable object, re-read on each render. */
|
||||
const state = {
|
||||
currentStep: 1,
|
||||
menu,
|
||||
returnCategory,
|
||||
burgers,
|
||||
accompagnements,
|
||||
boissons,
|
||||
sauces,
|
||||
/* Selections */
|
||||
burger: preselectedBurger,
|
||||
burgerOptions: [], // subset of ['sans-oignon', 'avec-fromage']
|
||||
accompagnement: accompagnements[0] ?? null,
|
||||
accompTaille: 'N', // 'N' or 'G'
|
||||
boisson: boissons[0] ?? null,
|
||||
boissonTaille: 'N',
|
||||
sauce: sauces[0] ?? null
|
||||
};
|
||||
|
||||
const modal = buildModalShell(menu);
|
||||
document.body.appendChild(modal);
|
||||
modal.removeAttribute('hidden');
|
||||
|
||||
/* Prevent background scroll while composer is open. */
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
renderStep(modal, state);
|
||||
trapFocus(modal);
|
||||
|
||||
/* ESC closes the modal and returns to product list. */
|
||||
const escHandler = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
cancelComposer(modal, returnCategory, escHandler);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', escHandler);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Modal shell builder */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function buildModalShell(menu) {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'composer-overlay';
|
||||
overlay.setAttribute('role', 'dialog');
|
||||
overlay.setAttribute('aria-modal', 'true');
|
||||
overlay.setAttribute('aria-labelledby', 'composer-title');
|
||||
overlay.hidden = true;
|
||||
|
||||
overlay.innerHTML = `
|
||||
<div class="composer-container" role="document">
|
||||
<div class="composer-header">
|
||||
<h2 class="composer-title" id="composer-title">${escHtml(menu.nom)}</h2>
|
||||
<div class="composer-progress" aria-label="Progression">
|
||||
<span class="composer-progress__text" id="composer-step-indicator" aria-live="polite">Etape 1 / ${TOTAL_STEPS}</span>
|
||||
<div class="composer-progress__bar">
|
||||
<div class="composer-progress__fill" id="composer-progress-fill" style="width: 20%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="composer-body" id="composer-body">
|
||||
<!-- step content injected here -->
|
||||
</div>
|
||||
<div class="composer-footer" id="composer-footer">
|
||||
<!-- navigation buttons injected here -->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return overlay;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Step renderer — decides which step to paint */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function renderStep(modal, state) {
|
||||
const body = modal.querySelector('#composer-body');
|
||||
const footer = modal.querySelector('#composer-footer');
|
||||
const stepEl = modal.querySelector('#composer-step-indicator');
|
||||
const fillEl = modal.querySelector('#composer-progress-fill');
|
||||
|
||||
stepEl.textContent = `Etape ${state.currentStep} / ${TOTAL_STEPS}`;
|
||||
fillEl.style.width = `${(state.currentStep / TOTAL_STEPS) * 100}%`;
|
||||
|
||||
/* Each step renderer returns {bodyHTML, canAdvance()} and may attach
|
||||
* its own event listeners after DOM insertion. */
|
||||
switch (state.currentStep) {
|
||||
case 1: renderStep1(body, footer, modal, state); break;
|
||||
case 2: renderStep2(body, footer, modal, state); break;
|
||||
case 3: renderStep3(body, footer, modal, state); break;
|
||||
case 4: renderStep4(body, footer, modal, state); break;
|
||||
case 5: renderStep5(body, footer, modal, state); break;
|
||||
}
|
||||
|
||||
/* Move focus to the first interactive element so keyboard users and
|
||||
* screen readers start at the right place after each step transition. */
|
||||
requestAnimationFrame(() => {
|
||||
const first = modal.querySelector(
|
||||
'button:not([disabled]), input:not([disabled]), [tabindex="0"]'
|
||||
);
|
||||
if (first) first.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Step 1 — Burger + personalisation options */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function renderStep1(body, footer, modal, state) {
|
||||
body.innerHTML = `
|
||||
<p class="composer-step__subtitle">Choisissez votre burger</p>
|
||||
<ul class="composer-grid" role="list" id="burger-grid">
|
||||
${state.burgers.map(b => `
|
||||
<li>
|
||||
<button
|
||||
class="composer-card ${state.burger && state.burger.id === b.id ? 'composer-card--selected' : ''}"
|
||||
type="button"
|
||||
data-id="${b.id}"
|
||||
aria-pressed="${state.burger && state.burger.id === b.id ? 'true' : 'false'}"
|
||||
aria-label="${escHtml(b.nom)}, ${formatPrice(b.prix)}"
|
||||
>
|
||||
<img
|
||||
class="composer-card__image"
|
||||
src="${escHtml(b.image)}"
|
||||
alt="${escHtml(b.nom)}"
|
||||
onerror="this.src='assets/images/ui/logo.png';"
|
||||
>
|
||||
<span class="composer-card__name">${escHtml(b.nom)}</span>
|
||||
<span class="composer-card__price">${formatPrice(b.prix)}</span>
|
||||
</button>
|
||||
</li>
|
||||
`).join('')}
|
||||
</ul>
|
||||
|
||||
<fieldset class="composer-options" id="burger-options">
|
||||
<legend class="composer-options__legend">Personnalisation</legend>
|
||||
<label class="composer-option-label">
|
||||
<input type="checkbox" name="burger-opt" value="sans-oignon"
|
||||
${state.burgerOptions.includes('sans-oignon') ? 'checked' : ''}>
|
||||
Sans oignon
|
||||
</label>
|
||||
<label class="composer-option-label">
|
||||
<input type="checkbox" name="burger-opt" value="avec-fromage"
|
||||
${state.burgerOptions.includes('avec-fromage') ? 'checked' : ''}>
|
||||
Avec fromage
|
||||
</label>
|
||||
</fieldset>
|
||||
`;
|
||||
|
||||
/* Burger card selection */
|
||||
body.querySelectorAll('#burger-grid .composer-card').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = parseInt(btn.dataset.id, 10);
|
||||
state.burger = state.burgers.find(b => b.id === id) ?? state.burger;
|
||||
/* Update pressed states without full re-render to preserve scroll position */
|
||||
body.querySelectorAll('#burger-grid .composer-card').forEach(b => {
|
||||
const active = parseInt(b.dataset.id, 10) === state.burger.id;
|
||||
b.classList.toggle('composer-card--selected', active);
|
||||
b.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* Personalisation checkboxes */
|
||||
body.querySelectorAll('input[name="burger-opt"]').forEach(cb => {
|
||||
cb.addEventListener('change', () => {
|
||||
state.burgerOptions = Array.from(
|
||||
body.querySelectorAll('input[name="burger-opt"]:checked')
|
||||
).map(el => el.value);
|
||||
});
|
||||
});
|
||||
|
||||
renderFooter(footer, modal, state, {
|
||||
canAdvance: () => state.burger !== null
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Step 2 — Accompagnement + taille toggle */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function renderStep2(body, footer, modal, state) {
|
||||
body.innerHTML = `
|
||||
<p class="composer-step__subtitle">Choisissez votre accompagnement</p>
|
||||
<ul class="composer-grid" role="list" id="accomp-grid">
|
||||
${state.accompagnements.map(a => `
|
||||
<li>
|
||||
<button
|
||||
class="composer-card ${state.accompagnement && state.accompagnement.id === a.id ? 'composer-card--selected' : ''}"
|
||||
type="button"
|
||||
data-id="${a.id}"
|
||||
aria-pressed="${state.accompagnement && state.accompagnement.id === a.id ? 'true' : 'false'}"
|
||||
aria-label="${escHtml(a.nom)}"
|
||||
>
|
||||
<img
|
||||
class="composer-card__image"
|
||||
src="${escHtml(a.image)}"
|
||||
alt="${escHtml(a.nom)}"
|
||||
onerror="this.src='assets/images/ui/logo.png';"
|
||||
>
|
||||
<span class="composer-card__name">${escHtml(a.nom)}</span>
|
||||
</button>
|
||||
</li>
|
||||
`).join('')}
|
||||
</ul>
|
||||
${renderTailleToggle('accomp', state.accompTaille)}
|
||||
`;
|
||||
|
||||
body.querySelectorAll('#accomp-grid .composer-card').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = parseInt(btn.dataset.id, 10);
|
||||
state.accompagnement = state.accompagnements.find(a => a.id === id) ?? state.accompagnement;
|
||||
body.querySelectorAll('#accomp-grid .composer-card').forEach(b => {
|
||||
const active = parseInt(b.dataset.id, 10) === state.accompagnement.id;
|
||||
b.classList.toggle('composer-card--selected', active);
|
||||
b.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
attachTailleToggle(body, 'accomp', state, 'accompTaille');
|
||||
|
||||
renderFooter(footer, modal, state, {
|
||||
canAdvance: () => state.accompagnement !== null
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Step 3 — Boisson + taille toggle */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function renderStep3(body, footer, modal, state) {
|
||||
body.innerHTML = `
|
||||
<p class="composer-step__subtitle">Choisissez votre boisson</p>
|
||||
<ul class="composer-grid" role="list" id="boisson-grid">
|
||||
${state.boissons.map(b => `
|
||||
<li>
|
||||
<button
|
||||
class="composer-card ${state.boisson && state.boisson.id === b.id ? 'composer-card--selected' : ''}"
|
||||
type="button"
|
||||
data-id="${b.id}"
|
||||
aria-pressed="${state.boisson && state.boisson.id === b.id ? 'true' : 'false'}"
|
||||
aria-label="${escHtml(b.nom)}"
|
||||
>
|
||||
<img
|
||||
class="composer-card__image"
|
||||
src="${escHtml(b.image)}"
|
||||
alt="${escHtml(b.nom)}"
|
||||
onerror="this.src='assets/images/ui/logo.png';"
|
||||
>
|
||||
<span class="composer-card__name">${escHtml(b.nom)}</span>
|
||||
</button>
|
||||
</li>
|
||||
`).join('')}
|
||||
</ul>
|
||||
${renderTailleToggle('boisson', state.boissonTaille)}
|
||||
`;
|
||||
|
||||
body.querySelectorAll('#boisson-grid .composer-card').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = parseInt(btn.dataset.id, 10);
|
||||
state.boisson = state.boissons.find(b => b.id === id) ?? state.boisson;
|
||||
body.querySelectorAll('#boisson-grid .composer-card').forEach(b => {
|
||||
const active = parseInt(b.dataset.id, 10) === state.boisson.id;
|
||||
b.classList.toggle('composer-card--selected', active);
|
||||
b.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
attachTailleToggle(body, 'boisson', state, 'boissonTaille');
|
||||
|
||||
renderFooter(footer, modal, state, {
|
||||
canAdvance: () => state.boisson !== null
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Step 4 — Sauce */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function renderStep4(body, footer, modal, state) {
|
||||
body.innerHTML = `
|
||||
<p class="composer-step__subtitle">Choisissez votre sauce</p>
|
||||
<ul class="composer-grid" role="list" id="sauce-grid">
|
||||
${state.sauces.map(s => `
|
||||
<li>
|
||||
<button
|
||||
class="composer-card ${state.sauce && state.sauce.id === s.id ? 'composer-card--selected' : ''}"
|
||||
type="button"
|
||||
data-id="${s.id}"
|
||||
aria-pressed="${state.sauce && state.sauce.id === s.id ? 'true' : 'false'}"
|
||||
aria-label="${escHtml(s.nom)}"
|
||||
>
|
||||
<img
|
||||
class="composer-card__image"
|
||||
src="${escHtml(s.image)}"
|
||||
alt="${escHtml(s.nom)}"
|
||||
onerror="this.src='assets/images/ui/logo.png';"
|
||||
>
|
||||
<span class="composer-card__name">${escHtml(s.nom)}</span>
|
||||
</button>
|
||||
</li>
|
||||
`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
|
||||
body.querySelectorAll('#sauce-grid .composer-card').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = parseInt(btn.dataset.id, 10);
|
||||
state.sauce = state.sauces.find(s => s.id === id) ?? state.sauce;
|
||||
body.querySelectorAll('#sauce-grid .composer-card').forEach(b => {
|
||||
const active = parseInt(b.dataset.id, 10) === state.sauce.id;
|
||||
b.classList.toggle('composer-card--selected', active);
|
||||
b.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
renderFooter(footer, modal, state, {
|
||||
canAdvance: () => state.sauce !== null
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Step 5 — Recap + add to cart */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function renderStep5(body, footer, modal, state) {
|
||||
const supplement = computeSupplement(state);
|
||||
const baseItem = buildCartItem(state, supplement);
|
||||
const totalLine = computeMenuLineCents(baseItem);
|
||||
|
||||
const optionsText = state.burgerOptions.length
|
||||
? state.burgerOptions.map(o => o === 'sans-oignon' ? 'sans oignon' : 'avec fromage').join(', ')
|
||||
: null;
|
||||
|
||||
body.innerHTML = `
|
||||
<p class="composer-step__subtitle">Recapitulatif de votre menu</p>
|
||||
<ul class="composer-recap" aria-label="Composition du menu">
|
||||
<li class="composer-recap__line">
|
||||
<span class="composer-recap__icon" aria-hidden="true">■</span>
|
||||
<span class="composer-recap__label">
|
||||
${escHtml(state.burger.nom)}
|
||||
${optionsText ? `<span class="composer-recap__opts">(${escHtml(optionsText)})</span>` : ''}
|
||||
</span>
|
||||
</li>
|
||||
<li class="composer-recap__line">
|
||||
<span class="composer-recap__icon" aria-hidden="true">■</span>
|
||||
<span class="composer-recap__label">
|
||||
${escHtml(state.accompagnement.nom)}
|
||||
<span class="composer-recap__taille">${state.accompTaille === 'G' ? 'grande' : 'normale'}</span>
|
||||
${state.accompTaille === 'G' ? '<span class="composer-recap__suppl">+0,50 EUR</span>' : ''}
|
||||
</span>
|
||||
</li>
|
||||
<li class="composer-recap__line">
|
||||
<span class="composer-recap__icon" aria-hidden="true">■</span>
|
||||
<span class="composer-recap__label">
|
||||
${escHtml(state.boisson.nom)}
|
||||
<span class="composer-recap__taille">${state.boissonTaille === 'G' ? 'grande' : 'normale'}</span>
|
||||
${state.boissonTaille === 'G' ? '<span class="composer-recap__suppl">+0,50 EUR</span>' : ''}
|
||||
</span>
|
||||
</li>
|
||||
<li class="composer-recap__line">
|
||||
<span class="composer-recap__icon" aria-hidden="true">■</span>
|
||||
<span class="composer-recap__label">${escHtml(state.sauce.nom)}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="composer-recap__totals">
|
||||
<span class="composer-recap__base">Menu de base : ${formatPrice(state.menu.prix_cents ?? state.menu.prix)}</span>
|
||||
${supplement > 0 ? `<span class="composer-recap__suppl-total">Supplement grande(s) taille(s) : +${formatPrice(supplement)}</span>` : ''}
|
||||
<span class="composer-recap__total-line">Total : <strong>${formatPrice(totalLine)}</strong></span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
footer.innerHTML = `
|
||||
<div class="composer-footer__row">
|
||||
<button class="btn btn--secondary composer-footer__cancel" type="button" id="composer-cancel">
|
||||
Annuler
|
||||
</button>
|
||||
<button class="btn btn--secondary composer-footer__prev" type="button" id="composer-prev">
|
||||
Precedent
|
||||
</button>
|
||||
<button class="btn btn--primary composer-footer__add" type="button" id="composer-add">
|
||||
Ajouter au panier
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
footer.querySelector('#composer-cancel').addEventListener('click', () => {
|
||||
cancelComposer(modal, state.returnCategory, null);
|
||||
});
|
||||
|
||||
footer.querySelector('#composer-prev').addEventListener('click', () => {
|
||||
state.currentStep--;
|
||||
renderStep(modal, state);
|
||||
});
|
||||
|
||||
footer.querySelector('#composer-add').addEventListener('click', () => {
|
||||
addToCart(baseItem);
|
||||
refreshCartBadge();
|
||||
closeComposer(modal);
|
||||
window.location.href = `products.html?category=${state.returnCategory}`;
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Footer renderer (steps 1-4) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Renders the navigation footer for steps 1 through 4.
|
||||
* @param {HTMLElement} footer
|
||||
* @param {HTMLElement} modal
|
||||
* @param {Object} state
|
||||
* @param {{ canAdvance: () => boolean }} opts
|
||||
*/
|
||||
function renderFooter(footer, modal, state, opts) {
|
||||
const isFirst = state.currentStep === 1;
|
||||
|
||||
footer.innerHTML = `
|
||||
<div class="composer-footer__row">
|
||||
<button class="btn btn--secondary composer-footer__cancel" type="button" id="composer-cancel">
|
||||
Annuler
|
||||
</button>
|
||||
${!isFirst ? `
|
||||
<button class="btn btn--secondary composer-footer__prev" type="button" id="composer-prev">
|
||||
Precedent
|
||||
</button>` : ''}
|
||||
<button class="btn btn--primary composer-footer__next" type="button" id="composer-next">
|
||||
Suivant
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
footer.querySelector('#composer-cancel').addEventListener('click', () => {
|
||||
cancelComposer(modal, state.returnCategory, null);
|
||||
});
|
||||
|
||||
if (!isFirst) {
|
||||
footer.querySelector('#composer-prev').addEventListener('click', () => {
|
||||
state.currentStep--;
|
||||
renderStep(modal, state);
|
||||
});
|
||||
}
|
||||
|
||||
footer.querySelector('#composer-next').addEventListener('click', () => {
|
||||
if (!opts.canAdvance()) return;
|
||||
state.currentStep++;
|
||||
renderStep(modal, state);
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Taille toggle — shared between accompagnement and boisson steps */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Generates the HTML for the Normale/Grande toggle.
|
||||
* @param {string} prefix — 'accomp' or 'boisson', used for IDs
|
||||
* @param {'N'|'G'} currentTaille
|
||||
* @returns {string}
|
||||
*/
|
||||
function renderTailleToggle(prefix, currentTaille) {
|
||||
return `
|
||||
<div class="composer-taille" role="group" aria-label="Taille">
|
||||
<button
|
||||
class="composer-taille__btn ${currentTaille === 'N' ? 'composer-taille__btn--active' : ''}"
|
||||
type="button"
|
||||
data-taille="N"
|
||||
id="${prefix}-taille-n"
|
||||
aria-pressed="${currentTaille === 'N' ? 'true' : 'false'}"
|
||||
>
|
||||
Normale
|
||||
</button>
|
||||
<button
|
||||
class="composer-taille__btn ${currentTaille === 'G' ? 'composer-taille__btn--active' : ''}"
|
||||
type="button"
|
||||
data-taille="G"
|
||||
id="${prefix}-taille-g"
|
||||
aria-pressed="${currentTaille === 'G' ? 'true' : 'false'}"
|
||||
>
|
||||
Grande <span class="composer-taille__price-hint">+0,50 EUR</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches click handlers to the taille toggle buttons and keeps state in sync.
|
||||
* @param {HTMLElement} body
|
||||
* @param {string} prefix
|
||||
* @param {Object} state
|
||||
* @param {'accompTaille'|'boissonTaille'} stateKey
|
||||
*/
|
||||
function attachTailleToggle(body, prefix, state, stateKey) {
|
||||
body.querySelectorAll('.composer-taille__btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
state[stateKey] = btn.dataset.taille;
|
||||
body.querySelectorAll('.composer-taille__btn').forEach(b => {
|
||||
const active = b.dataset.taille === state[stateKey];
|
||||
b.classList.toggle('composer-taille__btn--active', active);
|
||||
b.setAttribute('aria-pressed', active ? 'true' : 'false');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Cart item assembly + supplement calculation */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Counts how many grande-taille choices were made (0, 1, or 2).
|
||||
* @param {Object} state
|
||||
* @returns {number} centimes
|
||||
*/
|
||||
function computeSupplement(state) {
|
||||
let suppl = 0;
|
||||
if (state.accompTaille === 'G') suppl += SUPPLEMENT_GRANDE_CENTS;
|
||||
if (state.boissonTaille === 'G') suppl += SUPPLEMENT_GRANDE_CENTS;
|
||||
return suppl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the cart item object from the current composer state.
|
||||
* prix_cents is the base menu price; supplement_cents accumulates size upgrades.
|
||||
*
|
||||
* @param {Object} state
|
||||
* @param {number} supplement
|
||||
* @returns {Object}
|
||||
*/
|
||||
function buildCartItem(state, supplement) {
|
||||
/* Support both raw produits.json field (prix) and normalised (prix_cents) */
|
||||
const prixCents = state.menu.prix_cents ?? state.menu.prix;
|
||||
|
||||
return {
|
||||
id: state.menu.id,
|
||||
type: 'menu',
|
||||
categorie: 'menus',
|
||||
libelle: state.menu.nom,
|
||||
prix_cents: prixCents,
|
||||
quantite: 1,
|
||||
image: state.menu.image,
|
||||
supplement_cents: supplement,
|
||||
composition: {
|
||||
burger: {
|
||||
id: state.burger.id,
|
||||
libelle: state.burger.nom,
|
||||
options: [...state.burgerOptions]
|
||||
},
|
||||
accompagnement: {
|
||||
id: state.accompagnement.id,
|
||||
libelle: state.accompagnement.nom,
|
||||
categorie: state.accompagnement.categorie ?? 'frites',
|
||||
taille: state.accompTaille
|
||||
},
|
||||
boisson: {
|
||||
id: state.boisson.id,
|
||||
libelle: state.boisson.nom,
|
||||
taille: state.boissonTaille
|
||||
},
|
||||
sauce: {
|
||||
id: state.sauce.id,
|
||||
libelle: state.sauce.nom
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Focus trap */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Traps Tab / Shift+Tab inside the modal container.
|
||||
* The handler is attached to the modal element itself; it is removed
|
||||
* automatically when the modal is removed from the DOM.
|
||||
*/
|
||||
function trapFocus(modal) {
|
||||
modal.addEventListener('keydown', (e) => {
|
||||
if (e.key !== 'Tab') return;
|
||||
|
||||
const focusable = Array.from(modal.querySelectorAll(
|
||||
'button:not([disabled]), input:not([disabled]), [tabindex="0"]'
|
||||
)).filter(el => !el.closest('[hidden]'));
|
||||
|
||||
if (!focusable.length) return;
|
||||
|
||||
const first = focusable[0];
|
||||
const last = focusable[focusable.length - 1];
|
||||
|
||||
if (e.shiftKey) {
|
||||
if (document.activeElement === first) {
|
||||
e.preventDefault();
|
||||
last.focus();
|
||||
}
|
||||
} else {
|
||||
if (document.activeElement === last) {
|
||||
e.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Close helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function closeComposer(modal) {
|
||||
modal.remove();
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function cancelComposer(modal, returnCategory, escHandler) {
|
||||
if (escHandler) {
|
||||
document.removeEventListener('keydown', escHandler);
|
||||
}
|
||||
closeComposer(modal);
|
||||
window.location.href = `products.html?category=${returnCategory}`;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Utilities */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Minimal HTML escaping to prevent XSS when injecting product names/paths
|
||||
* into innerHTML. Applied to all data-derived strings.
|
||||
*/
|
||||
function escHtml(str) {
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* page-product.js — Product detail screen.
|
||||
*
|
||||
* Reads ?id=<int>&category=<slug> from the query string.
|
||||
*
|
||||
* Branch on product type:
|
||||
* - type === 'menu' → open the multi-step composer modal (page-product-menu.js).
|
||||
* The standard detail layout is bypassed because a menu
|
||||
* cannot be added to the cart without composition choices.
|
||||
* - type === 'produit' → render the standard detail card with "Ajouter au panier".
|
||||
*
|
||||
* After "Ajouter au panier" (simple product):
|
||||
* 1. Item added to cart via state.addToCart()
|
||||
* 2. Button changes to "Ajoute !" for 1 second (visual feedback)
|
||||
* 3. Redirect to products.html?category=<slug>
|
||||
*/
|
||||
|
||||
import { findProduct } from './data.js';
|
||||
import { addToCart, formatPrice } from './state.js';
|
||||
import { refreshCartBadge } from './nav.js';
|
||||
import { openMenuComposer } from './page-product-menu.js';
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const productId = parseInt(params.get('id'), 10);
|
||||
const categorySlug = params.get('category') ?? 'menus';
|
||||
|
||||
const container = document.getElementById('product-detail');
|
||||
const errorBlock = document.getElementById('product-error');
|
||||
const backBtn = document.getElementById('back-to-products');
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.href = `products.html?category=${categorySlug}`;
|
||||
}
|
||||
|
||||
async function renderProduct() {
|
||||
if (!productId) {
|
||||
showError('Produit introuvable.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const product = await findProduct(productId);
|
||||
if (!product) {
|
||||
showError('Ce produit n\'existe pas.');
|
||||
return;
|
||||
}
|
||||
|
||||
document.title = `Wakdo - ${product.nom}`;
|
||||
|
||||
if (product.type === 'menu') {
|
||||
/* Hide the standard product detail area; the composer will overlay the page.
|
||||
* The container stays in the DOM so the skeleton does not flash. */
|
||||
container.hidden = true;
|
||||
await openMenuComposer(product, categorySlug);
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="product-detail__image-wrap">
|
||||
<img
|
||||
class="product-detail__image"
|
||||
src="${product.image}"
|
||||
alt="${product.nom}"
|
||||
onerror="this.src='assets/images/ui/logo.png'; this.alt='Image non disponible';"
|
||||
>
|
||||
</div>
|
||||
<div class="product-detail__info">
|
||||
<h1 class="product-detail__name">${product.nom}</h1>
|
||||
<p class="product-detail__price">${formatPrice(product.prix)}</p>
|
||||
<button
|
||||
class="btn btn--primary btn--large product-detail__add"
|
||||
id="add-to-cart-btn"
|
||||
aria-label="Ajouter ${product.nom} au panier"
|
||||
type="button"
|
||||
>
|
||||
Ajouter au panier
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('add-to-cart-btn').addEventListener('click', () => {
|
||||
addToCart({
|
||||
id: product.id,
|
||||
type: product.type,
|
||||
categorie: product.categorie ?? categorySlug,
|
||||
libelle: product.nom,
|
||||
prix_cents: product.prix,
|
||||
quantite: 1,
|
||||
image: product.image
|
||||
});
|
||||
refreshCartBadge();
|
||||
|
||||
const btn = document.getElementById('add-to-cart-btn');
|
||||
btn.textContent = 'Ajoute !';
|
||||
btn.disabled = true;
|
||||
|
||||
/* Redirect after brief confirmation pause */
|
||||
setTimeout(() => {
|
||||
window.location.href = `products.html?category=${categorySlug}`;
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
showError('Erreur lors du chargement du produit.');
|
||||
console.error('renderProduct error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
if (errorBlock) {
|
||||
errorBlock.hidden = false;
|
||||
errorBlock.textContent = msg;
|
||||
}
|
||||
if (container) {
|
||||
container.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', renderProduct);
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* page-products.js — Products list screen.
|
||||
*
|
||||
* Reads ?category=<id> from the query string, maps to a slug via
|
||||
* CATEGORY_ID_TO_SLUG, then fetches the matching product array.
|
||||
* On product card click, navigates to product.html?id=<id>&category=<slug>.
|
||||
*/
|
||||
|
||||
import { getProductsByCategory, getCategoryById, CATEGORY_ID_TO_SLUG } from './data.js';
|
||||
import { formatPrice } from './state.js';
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const categoryId = parseInt(params.get('category'), 10) || 1;
|
||||
const categorySlug = CATEGORY_ID_TO_SLUG[categoryId] ?? 'menus';
|
||||
|
||||
const grid = document.getElementById('products-grid');
|
||||
const heading = document.getElementById('products-heading');
|
||||
const backBtn = document.getElementById('back-to-categories');
|
||||
const errorBlock = document.getElementById('products-error');
|
||||
|
||||
/* Build back URL preserving mode query param if present */
|
||||
const modeParam = params.get('mode');
|
||||
|
||||
function buildBackURL() {
|
||||
const base = 'categories.html';
|
||||
return modeParam ? `${base}?mode=${modeParam}` : base;
|
||||
}
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.href = buildBackURL();
|
||||
}
|
||||
|
||||
async function renderProducts() {
|
||||
try {
|
||||
const [products, category] = await Promise.all([
|
||||
getProductsByCategory(categorySlug),
|
||||
getCategoryById(categoryId)
|
||||
]);
|
||||
|
||||
if (heading && category) {
|
||||
/* Capitalize first letter of the category title */
|
||||
const title = category.title.charAt(0).toUpperCase() + category.title.slice(1);
|
||||
heading.textContent = `Nos ${title}`;
|
||||
document.title = `Wakdo - ${title}`;
|
||||
}
|
||||
|
||||
if (!products.length) {
|
||||
grid.innerHTML = '<p class="products-empty">Aucun produit disponible dans cette categorie.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = '';
|
||||
products.forEach(product => {
|
||||
const card = document.createElement('a');
|
||||
card.className = 'product-card';
|
||||
card.href = `product.html?id=${product.id}&category=${categorySlug}`;
|
||||
card.setAttribute('aria-label', `${product.nom} - ${formatPrice(product.prix)}`);
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="product-card__image-wrap">
|
||||
<img
|
||||
class="product-card__image"
|
||||
src="${product.image}"
|
||||
alt="${product.nom}"
|
||||
loading="lazy"
|
||||
onerror="this.src='assets/images/ui/logo.png'; this.alt='Image non disponible';"
|
||||
>
|
||||
</div>
|
||||
<div class="product-card__body">
|
||||
<span class="product-card__name">${product.nom}</span>
|
||||
<span class="product-card__price">${formatPrice(product.prix)}</span>
|
||||
</div>
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
if (errorBlock) {
|
||||
errorBlock.hidden = false;
|
||||
errorBlock.textContent = 'Impossible de charger les produits. Veuillez reessayer.';
|
||||
}
|
||||
console.error('renderProducts error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', renderProducts);
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
/*
|
||||
* state.js — Global client-side state for the Wakdo kiosk.
|
||||
*
|
||||
* Persists via localStorage so that navigation between pages does not
|
||||
* lose the cart or the consumption mode.
|
||||
*
|
||||
* Price convention: all values stored and computed in INTEGER CENTIMES.
|
||||
* Formatting for display is handled by formatPrice().
|
||||
*
|
||||
* TVA note: 10% applied at display time in cart/payment pages only.
|
||||
* This is a simplified rate for restaurant consumption (France 2024).
|
||||
* TODO: verify exact applicable rate with an accountant in P3 — the real
|
||||
* rate depends on sur-place vs a-emporter, alcohol content, etc.
|
||||
*/
|
||||
|
||||
const STORAGE_KEY_MODE = 'wakdo_mode';
|
||||
const STORAGE_KEY_CART = 'wakdo_cart';
|
||||
|
||||
/* --- Consumption mode ---------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Returns the stored consumption mode string or null if not yet chosen.
|
||||
* @returns {'sur-place'|'a-emporter'|null}
|
||||
*/
|
||||
export function getMode() {
|
||||
return localStorage.getItem(STORAGE_KEY_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the consumption mode chosen on the welcome screen.
|
||||
* @param {'sur-place'|'a-emporter'} mode
|
||||
*/
|
||||
export function setMode(mode) {
|
||||
localStorage.setItem(STORAGE_KEY_MODE, mode);
|
||||
}
|
||||
|
||||
/* --- Cart state ---------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Returns the current cart array.
|
||||
* Each item shape:
|
||||
* { id, type: 'produit'|'menu', categorie, libelle, prix_cents, quantite, image }
|
||||
* @returns {Array}
|
||||
*/
|
||||
export function getCart() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(STORAGE_KEY_CART)) || [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the entire cart.
|
||||
* @param {Array} items
|
||||
*/
|
||||
export function setCart(items) {
|
||||
localStorage.setItem(STORAGE_KEY_CART, JSON.stringify(items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a product or menu to the cart.
|
||||
*
|
||||
* For simple products (type !== 'menu'), merges with an existing line
|
||||
* of the same id — matching real kiosk behavior where two identical
|
||||
* sandwiches become one line with qty 2.
|
||||
*
|
||||
* For composed menus, each call always creates a new line because two
|
||||
* menus with identical base id may have different compositions (different
|
||||
* burger options, sizes, sauces). This prevents silent composition loss.
|
||||
*
|
||||
* Item shapes:
|
||||
* Simple: { id, type, categorie, libelle, prix_cents, quantite, image }
|
||||
* Menu: { ...above, composition: {...}, supplement_cents: number }
|
||||
*
|
||||
* @param {Object} item
|
||||
*/
|
||||
export function addToCart(item) {
|
||||
const cart = getCart();
|
||||
if (item.type !== 'menu') {
|
||||
const existing = cart.find(c => c.id === item.id && c.type === item.type);
|
||||
if (existing) {
|
||||
existing.quantite += item.quantite ?? 1;
|
||||
setCart(cart);
|
||||
return;
|
||||
}
|
||||
}
|
||||
cart.push({ quantite: 1, ...item });
|
||||
setCart(cart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the item at the given index from the cart.
|
||||
* @param {number} index
|
||||
*/
|
||||
export function removeFromCart(index) {
|
||||
const cart = getCart();
|
||||
cart.splice(index, 1);
|
||||
setCart(cart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the quantity for the item at the given index.
|
||||
* If qty reaches 0, the item is removed.
|
||||
* @param {number} index
|
||||
* @param {number} qty
|
||||
*/
|
||||
export function updateQuantity(index, qty) {
|
||||
const cart = getCart();
|
||||
if (qty <= 0) {
|
||||
cart.splice(index, 1);
|
||||
} else {
|
||||
cart[index].quantite = qty;
|
||||
}
|
||||
setCart(cart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the cart completely.
|
||||
*/
|
||||
export function clearCart() {
|
||||
localStorage.removeItem(STORAGE_KEY_CART);
|
||||
}
|
||||
|
||||
/* --- Totals -------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Computes the line total in centimes for a menu item including size supplements.
|
||||
* For simple product items the caller should use (prix_cents * quantite) directly.
|
||||
* @param {{ prix_cents: number, supplement_cents: number, quantite: number }} item
|
||||
* @returns {number}
|
||||
*/
|
||||
export function computeMenuLineCents(item) {
|
||||
return (item.prix_cents + (item.supplement_cents ?? 0)) * item.quantite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sum of all line totals in centimes.
|
||||
* Menu items include their size supplements; simple items do not carry supplements.
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getTotalCents() {
|
||||
return getCart().reduce((sum, item) => {
|
||||
if (item.type === 'menu') {
|
||||
return sum + computeMenuLineCents(item);
|
||||
}
|
||||
return sum + item.prix_cents * item.quantite;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/* --- Formatting helpers -------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Formats a centimes integer into a French locale price string.
|
||||
* Example: 490 -> "4,90 EUR"
|
||||
* @param {number} cents
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatPrice(cents) {
|
||||
const euros = cents / 100;
|
||||
return euros.toLocaleString('fr-FR', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
}) + ' EUR';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item count (sum of all quantities) in the cart.
|
||||
* Used to show a badge on the cart button.
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getCartCount() {
|
||||
return getCart().reduce((sum, item) => sum + item.quantite, 0);
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="description" content="Wakdo - Votre panier">
|
||||
<title>Wakdo - Panier</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body class="cart-page">
|
||||
|
||||
<!--
|
||||
cart.html — Shopping cart.
|
||||
page-cart.js renders all cart lines, handles qty controls and removal.
|
||||
|
||||
TVA: 10% (restauration France 2024 — simplified rate).
|
||||
TODO: verify exact rate with accountant in P3 — actual rate depends
|
||||
on sur-place vs a-emporter and product type (alcohol, etc.).
|
||||
|
||||
The stored prices are TTC. HT is back-calculated at display time only.
|
||||
-->
|
||||
|
||||
<header class="site-header">
|
||||
<a
|
||||
class="site-header__back"
|
||||
href="categories.html"
|
||||
aria-label="Continuer mes achats"
|
||||
>
|
||||
← Continuer
|
||||
</a>
|
||||
<img
|
||||
class="site-header__logo"
|
||||
src="assets/images/ui/logo.png"
|
||||
alt="Wakdo"
|
||||
>
|
||||
<span class="mode-badge site-header__mode" data-mode-badge aria-label="Mode de consommation">Sur place</span>
|
||||
</header>
|
||||
|
||||
<main class="cart-main" aria-label="Votre panier">
|
||||
|
||||
<h1 class="cart-main__heading">Votre panier</h1>
|
||||
|
||||
<!-- Empty cart state -->
|
||||
<div id="cart-empty" class="cart-empty" hidden>
|
||||
<p class="cart-empty__message">Votre panier est vide.</p>
|
||||
<a class="btn btn--secondary" href="categories.html">Decouvrir nos produits</a>
|
||||
</div>
|
||||
|
||||
<!-- Cart lines -->
|
||||
<ul id="cart-list" class="cart-list" aria-label="Lignes du panier">
|
||||
<!-- Filled by page-cart.js -->
|
||||
</ul>
|
||||
|
||||
<!-- Order summary -->
|
||||
<aside id="cart-summary" class="cart-summary" hidden aria-label="Recapitulatif de commande">
|
||||
<div class="cart-summary__line">
|
||||
<span>Total HT</span>
|
||||
<span id="total-ht">—</span>
|
||||
</div>
|
||||
<div class="cart-summary__line">
|
||||
<!-- TVA 10% — taux restauration FR 2024 (simplifie, voir commentaire ci-dessus) -->
|
||||
<span>TVA (10%)</span>
|
||||
<span id="total-tva">—</span>
|
||||
</div>
|
||||
<div class="cart-summary__line cart-summary__line--total">
|
||||
<span>Total TTC</span>
|
||||
<strong id="total-ttc">—</strong>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="cart-actions">
|
||||
<button
|
||||
id="abandon-btn"
|
||||
class="btn btn--secondary"
|
||||
type="button"
|
||||
aria-label="Abandonner la commande et retourner aux categories"
|
||||
>
|
||||
Abandonner
|
||||
</button>
|
||||
<a
|
||||
id="pay-btn"
|
||||
class="btn btn--primary"
|
||||
href="payment.html"
|
||||
role="button"
|
||||
aria-label="Passer au paiement"
|
||||
aria-disabled="true"
|
||||
>
|
||||
Valider ma commande
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module" src="assets/js/nav.js"></script>
|
||||
<script type="module" src="assets/js/page-cart.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="description" content="Wakdo - Choisissez une categorie de produits">
|
||||
<title>Wakdo - Categories</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body class="categories-page">
|
||||
|
||||
<!--
|
||||
Categories screen.
|
||||
Data source: docs/merise/_sources/categories.json (9 categories).
|
||||
Image paths: assets/images/categories/{title}.png — verified against filesystem.
|
||||
In P4 this page will be generated dynamically from GET /api/categories.
|
||||
For now it is a static scaffold that matches the data contract exactly.
|
||||
-->
|
||||
|
||||
<header class="site-header">
|
||||
<a href="index.html" class="site-header__back" aria-label="Retour a l'accueil">
|
||||
← Retour
|
||||
</a>
|
||||
<img
|
||||
class="site-header__logo"
|
||||
src="assets/images/ui/logo.png"
|
||||
alt="Wakdo"
|
||||
>
|
||||
<!--
|
||||
Empty div keeps the logo centered via space-between.
|
||||
When the cart icon is added in P5, it replaces this placeholder.
|
||||
-->
|
||||
<div aria-hidden="true" style="width: 80px;"></div>
|
||||
</header>
|
||||
|
||||
<main class="categories-main" aria-label="Categories de produits">
|
||||
<h1 class="categories-main__heading">Que souhaitez-vous commander ?</h1>
|
||||
<p class="categories-main__sub">Choisissez une categorie pour decouvrir nos produits</p>
|
||||
|
||||
<!--
|
||||
9 categories from categories.json, in the same order as the source.
|
||||
Each card links to a product page (products.html?category=<id>) — stub URL
|
||||
for future P5 implementation. The link is functional HTML; no JS needed.
|
||||
title field from JSON used as alt text and visible label.
|
||||
-->
|
||||
<nav class="category-grid" aria-label="Navigation par categorie">
|
||||
|
||||
<!-- id: 1 | title: menus -->
|
||||
<a
|
||||
class="category-card"
|
||||
href="products.html?category=1"
|
||||
aria-label="Voir les menus"
|
||||
>
|
||||
<img
|
||||
class="category-card__image"
|
||||
src="assets/images/categories/menus.png"
|
||||
alt="Menus"
|
||||
>
|
||||
<span class="category-card__label">Menus</span>
|
||||
</a>
|
||||
|
||||
<!-- id: 2 | title: boissons -->
|
||||
<a
|
||||
class="category-card"
|
||||
href="products.html?category=2"
|
||||
aria-label="Voir les boissons"
|
||||
>
|
||||
<img
|
||||
class="category-card__image"
|
||||
src="assets/images/categories/boissons.png"
|
||||
alt="Boissons"
|
||||
>
|
||||
<span class="category-card__label">Boissons</span>
|
||||
</a>
|
||||
|
||||
<!-- id: 3 | title: burgers -->
|
||||
<a
|
||||
class="category-card"
|
||||
href="products.html?category=3"
|
||||
aria-label="Voir les burgers"
|
||||
>
|
||||
<img
|
||||
class="category-card__image"
|
||||
src="assets/images/categories/burgers.png"
|
||||
alt="Burgers"
|
||||
>
|
||||
<span class="category-card__label">Burgers</span>
|
||||
</a>
|
||||
|
||||
<!-- id: 4 | title: frites -->
|
||||
<a
|
||||
class="category-card"
|
||||
href="products.html?category=4"
|
||||
aria-label="Voir les frites"
|
||||
>
|
||||
<img
|
||||
class="category-card__image"
|
||||
src="assets/images/categories/frites.png"
|
||||
alt="Frites"
|
||||
>
|
||||
<span class="category-card__label">Frites</span>
|
||||
</a>
|
||||
|
||||
<!-- id: 5 | title: encas -->
|
||||
<a
|
||||
class="category-card"
|
||||
href="products.html?category=5"
|
||||
aria-label="Voir les encas"
|
||||
>
|
||||
<img
|
||||
class="category-card__image"
|
||||
src="assets/images/categories/encas.png"
|
||||
alt="Encas"
|
||||
>
|
||||
<span class="category-card__label">Encas</span>
|
||||
</a>
|
||||
|
||||
<!-- id: 6 | title: wraps -->
|
||||
<a
|
||||
class="category-card"
|
||||
href="products.html?category=6"
|
||||
aria-label="Voir les wraps"
|
||||
>
|
||||
<img
|
||||
class="category-card__image"
|
||||
src="assets/images/categories/wraps.png"
|
||||
alt="Wraps"
|
||||
>
|
||||
<span class="category-card__label">Wraps</span>
|
||||
</a>
|
||||
|
||||
<!-- id: 7 | title: salades -->
|
||||
<a
|
||||
class="category-card"
|
||||
href="products.html?category=7"
|
||||
aria-label="Voir les salades"
|
||||
>
|
||||
<img
|
||||
class="category-card__image"
|
||||
src="assets/images/categories/salades.png"
|
||||
alt="Salades"
|
||||
>
|
||||
<span class="category-card__label">Salades</span>
|
||||
</a>
|
||||
|
||||
<!-- id: 8 | title: desserts -->
|
||||
<a
|
||||
class="category-card"
|
||||
href="products.html?category=8"
|
||||
aria-label="Voir les desserts"
|
||||
>
|
||||
<img
|
||||
class="category-card__image"
|
||||
src="assets/images/categories/desserts.png"
|
||||
alt="Desserts"
|
||||
>
|
||||
<span class="category-card__label">Desserts</span>
|
||||
</a>
|
||||
|
||||
<!-- id: 9 | title: sauces -->
|
||||
<a
|
||||
class="category-card"
|
||||
href="products.html?category=9"
|
||||
aria-label="Voir les sauces"
|
||||
>
|
||||
<img
|
||||
class="category-card__image"
|
||||
src="assets/images/categories/sauces.png"
|
||||
alt="Sauces"
|
||||
>
|
||||
<span class="category-card__label">Sauces</span>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="description" content="Wakdo - Commande confirmee">
|
||||
<title>Wakdo - Confirmation</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body class="confirmation-page">
|
||||
|
||||
<!--
|
||||
confirmation.html — Order confirmed screen.
|
||||
Order number: "WK-" + Date.now().toString(36).toUpperCase()
|
||||
Cart is cleared on page load by page-confirmation.js.
|
||||
-->
|
||||
|
||||
<header class="site-header site-header--minimal">
|
||||
<img
|
||||
class="site-header__logo"
|
||||
src="assets/images/ui/logo.png"
|
||||
alt="Wakdo"
|
||||
>
|
||||
</header>
|
||||
|
||||
<main class="confirmation-main" aria-label="Confirmation de commande">
|
||||
|
||||
<div class="confirmation-banner" role="status" aria-live="polite">
|
||||
|
||||
<!-- Checkmark SVG (inline, no external dependency) -->
|
||||
<svg class="confirmation-banner__check" viewBox="0 0 80 80" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="40" cy="40" r="38" fill="#FFC72C"/>
|
||||
<polyline points="22,40 34,54 58,28" fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
<h1 class="confirmation-banner__title">Commande confirmee !</h1>
|
||||
|
||||
<p class="confirmation-banner__sub">Votre commande est en preparation</p>
|
||||
|
||||
<div class="confirmation-banner__number-block">
|
||||
<span class="confirmation-banner__number-label">Votre numero de commande</span>
|
||||
<strong class="confirmation-banner__number" id="order-number" aria-live="polite">—</strong>
|
||||
</div>
|
||||
|
||||
<p class="confirmation-banner__total">
|
||||
Montant regle : <strong id="order-total">—</strong>
|
||||
</p>
|
||||
|
||||
<p class="confirmation-banner__delay">
|
||||
Temps d'attente estime : <strong>5 - 10 minutes</strong>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="new-order-btn"
|
||||
class="btn btn--primary btn--large confirmation-new-order"
|
||||
type="button"
|
||||
aria-label="Demarrer une nouvelle commande"
|
||||
>
|
||||
Nouvelle commande
|
||||
</button>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module" src="assets/js/page-confirmation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
[
|
||||
{ "id": 1, "title": "menus", "slug": "menus", "image": "assets/images/categories/menus.png" },
|
||||
{ "id": 2, "title": "boissons", "slug": "boissons", "image": "assets/images/categories/boissons.png" },
|
||||
{ "id": 3, "title": "burgers", "slug": "burgers", "image": "assets/images/categories/burgers.png" },
|
||||
{ "id": 4, "title": "frites", "slug": "frites", "image": "assets/images/categories/frites.png" },
|
||||
{ "id": 5, "title": "encas", "slug": "encas", "image": "assets/images/categories/encas.png" },
|
||||
{ "id": 6, "title": "wraps", "slug": "wraps", "image": "assets/images/categories/wraps.png" },
|
||||
{ "id": 7, "title": "salades", "slug": "salades", "image": "assets/images/categories/salades.png" },
|
||||
{ "id": 8, "title": "desserts", "slug": "desserts", "image": "assets/images/categories/desserts.png" },
|
||||
{ "id": 9, "title": "sauces", "slug": "sauces", "image": "assets/images/categories/sauces.png" }
|
||||
]
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
{
|
||||
"menus": [
|
||||
{ "id": 1, "nom": "Menu Le 280", "prix": 880, "image": "assets/images/produits/burgers/280.png", "type": "menu" },
|
||||
{ "id": 2, "nom": "Menu Big Tasty", "prix": 1060, "image": "assets/images/produits/burgers/big-tasty-1-viande.png", "type": "menu" },
|
||||
{ "id": 3, "nom": "Menu Big Tasty Bacon", "prix": 1090, "image": "assets/images/produits/burgers/big-tasty-bacon-1-viande.png", "type": "menu" },
|
||||
{ "id": 4, "nom": "Menu Big Mac", "prix": 800, "image": "assets/images/produits/burgers/bigmac.png", "type": "menu" },
|
||||
{ "id": 5, "nom": "Menu CBO", "prix": 1090, "image": "assets/images/produits/burgers/cbo.png", "type": "menu" },
|
||||
{ "id": 6, "nom": "Menu MC Chicken", "prix": 930, "image": "assets/images/produits/burgers/mcchicken.png", "type": "menu" },
|
||||
{ "id": 7, "nom": "Menu MC Crispy", "prix": 720, "image": "assets/images/produits/burgers/mccrispy.png", "type": "menu" },
|
||||
{ "id": 8, "nom": "Menu MC Fish", "prix": 720, "image": "assets/images/produits/burgers/mcfish.png", "type": "menu" },
|
||||
{ "id": 9, "nom": "Menu Royal Bacon", "prix": 705, "image": "assets/images/produits/burgers/royalbacon.png", "type": "menu" },
|
||||
{ "id": 10, "nom": "Menu Royal Cheese", "prix": 640, "image": "assets/images/produits/burgers/royalcheese.png", "type": "menu" },
|
||||
{ "id": 11, "nom": "Menu Royal Deluxe", "prix": 740, "image": "assets/images/produits/burgers/royaldeluxe.png", "type": "menu" },
|
||||
{ "id": 12, "nom": "Menu Signature BBQ Beef 2 viandes","prix": 1350,"image": "assets/images/produits/burgers/signature-bbq-beef-2-viandes.png", "type": "menu" },
|
||||
{ "id": 13, "nom": "Menu Signature Beef BBQ", "prix": 1190, "image": "assets/images/produits/burgers/signature-beef-bbq-burger-1-viande.png", "type": "menu" }
|
||||
],
|
||||
"burgers": [
|
||||
{ "id": 14, "nom": "Le 280", "prix": 680, "image": "assets/images/produits/burgers/280.png", "type": "produit" },
|
||||
{ "id": 15, "nom": "Big Tasty", "prix": 860, "image": "assets/images/produits/burgers/big-tasty-1-viande.png", "type": "produit" },
|
||||
{ "id": 16, "nom": "Big Tasty Bacon", "prix": 890, "image": "assets/images/produits/burgers/big-tasty-bacon-1-viande.png", "type": "produit" },
|
||||
{ "id": 17, "nom": "Big Mac", "prix": 600, "image": "assets/images/produits/burgers/bigmac.png", "type": "produit" },
|
||||
{ "id": 18, "nom": "CBO", "prix": 890, "image": "assets/images/produits/burgers/cbo.png", "type": "produit" },
|
||||
{ "id": 19, "nom": "MC Chicken", "prix": 730, "image": "assets/images/produits/burgers/mcchicken.png", "type": "produit" },
|
||||
{ "id": 20, "nom": "MC Crispy", "prix": 530, "image": "assets/images/produits/burgers/mccrispy.png", "type": "produit" },
|
||||
{ "id": 21, "nom": "MC Fish", "prix": 485, "image": "assets/images/produits/burgers/mcfish.png", "type": "produit" },
|
||||
{ "id": 22, "nom": "Royal Bacon", "prix": 510, "image": "assets/images/produits/burgers/royalbacon.png", "type": "produit" },
|
||||
{ "id": 23, "nom": "Royal Cheese", "prix": 440, "image": "assets/images/produits/burgers/royalcheese.png", "type": "produit" },
|
||||
{ "id": 24, "nom": "Royal Deluxe", "prix": 540, "image": "assets/images/produits/burgers/royaldeluxe.png", "type": "produit" },
|
||||
{ "id": 25, "nom": "Signature BBQ Beef 2 viandes","prix": 1140,"image": "assets/images/produits/burgers/signature-bbq-beef-2-viandes.png","type": "produit" },
|
||||
{ "id": 26, "nom": "Signature Beef BBQ", "prix": 1030, "image": "assets/images/produits/burgers/signature-beef-bbq-burger-1-viande.png","type": "produit" }
|
||||
],
|
||||
"boissons": [
|
||||
{ "id": 27, "nom": "Coca Cola", "prix": 190, "image": "assets/images/produits/boissons/coca-cola.png", "type": "produit" },
|
||||
{ "id": 28, "nom": "Coca Sans Sucres", "prix": 190, "image": "assets/images/produits/boissons/coca-sans-sucres.png", "type": "produit" },
|
||||
{ "id": 29, "nom": "Eau", "prix": 100, "image": "assets/images/produits/boissons/eau.png", "type": "produit" },
|
||||
{ "id": 30, "nom": "Fanta Orange", "prix": 190, "image": "assets/images/produits/boissons/fanta.png", "type": "produit" },
|
||||
{ "id": 31, "nom": "Ice Tea Peche", "prix": 190, "image": "assets/images/produits/boissons/ice-tea-peche.png", "type": "produit" },
|
||||
{ "id": 32, "nom": "Ice Tea Citron", "prix": 190, "image": "assets/images/produits/boissons/the-vert-citron-sans-sucres.png", "type": "produit" },
|
||||
{ "id": 33, "nom": "Jus d'Orange", "prix": 210, "image": "assets/images/produits/boissons/jus-orange.png", "type": "produit" },
|
||||
{ "id": 34, "nom": "Jus de Pommes Bio", "prix": 230, "image": "assets/images/produits/boissons/jus-pomme-bio.png", "type": "produit" }
|
||||
],
|
||||
"frites": [
|
||||
{ "id": 35, "nom": "Petite Frite", "prix": 145, "image": "assets/images/produits/frites/petite-frite.png", "type": "produit" },
|
||||
{ "id": 36, "nom": "Moyenne Frite", "prix": 275, "image": "assets/images/produits/frites/moyenne-frite.png", "type": "produit" },
|
||||
{ "id": 37, "nom": "Grande Frite", "prix": 350, "image": "assets/images/produits/frites/grande-frite.png", "type": "produit" },
|
||||
{ "id": 38, "nom": "Potatoes", "prix": 215, "image": "assets/images/produits/frites/potatoes.png", "type": "produit" },
|
||||
{ "id": 39, "nom": "Grande Potatoes", "prix": 340, "image": "assets/images/produits/frites/grande-potatoes.png", "type": "produit" }
|
||||
],
|
||||
"encas": [
|
||||
{ "id": 40, "nom": "Cheeseburger", "prix": 260, "image": "assets/images/produits/encas/cheeseburger.png", "type": "produit" },
|
||||
{ "id": 41, "nom": "Croc MCdo", "prix": 320, "image": "assets/images/produits/encas/croc-mc-do.png", "type": "produit" },
|
||||
{ "id": 42, "nom": "Nuggets x4", "prix": 420, "image": "assets/images/produits/encas/nuggets-4.png", "type": "produit" },
|
||||
{ "id": 43, "nom": "Nuggets x20", "prix": 1300, "image": "assets/images/produits/encas/nuggets-20.png", "type": "produit" }
|
||||
],
|
||||
"desserts": [
|
||||
{ "id": 44, "nom": "Brownie", "prix": 260, "image": "assets/images/produits/desserts/brownies.png", "type": "produit" },
|
||||
{ "id": 45, "nom": "Cheesecake Chocolat M&M's","prix": 310, "image": "assets/images/produits/desserts/cheesecake-choconuts-m&m-s.png", "type": "produit" },
|
||||
{ "id": 46, "nom": "Cheesecake Fraise", "prix": 310, "image": "assets/images/produits/desserts/cheesecake-fraise.png", "type": "produit" },
|
||||
{ "id": 47, "nom": "Cookie", "prix": 320, "image": "assets/images/produits/desserts/cookie.png", "type": "produit" },
|
||||
{ "id": 48, "nom": "Donut", "prix": 260, "image": "assets/images/produits/desserts/doghnut.png", "type": "produit" },
|
||||
{ "id": 49, "nom": "Macarons", "prix": 270, "image": "assets/images/produits/desserts/macarons.png", "type": "produit" },
|
||||
{ "id": 50, "nom": "MC Fleury", "prix": 440, "image": "assets/images/produits/desserts/mcfleury.png", "type": "produit" },
|
||||
{ "id": 51, "nom": "Muffin", "prix": 360, "image": "assets/images/produits/desserts/muffin.png", "type": "produit" },
|
||||
{ "id": 52, "nom": "Sunday", "prix": 100, "image": "assets/images/produits/desserts/sunday.png", "type": "produit" }
|
||||
],
|
||||
"sauces": [
|
||||
{ "id": 53, "nom": "Classic Barbecue", "prix": 70, "image": "assets/images/produits/sauces/classic-barbecue.png", "type": "produit" },
|
||||
{ "id": 54, "nom": "Classic Moutarde", "prix": 70, "image": "assets/images/produits/sauces/classic-moutarde.png", "type": "produit" },
|
||||
{ "id": 55, "nom": "Creamy Deluxe", "prix": 70, "image": "assets/images/produits/sauces/cremy-deluxe.png", "type": "produit" },
|
||||
{ "id": 56, "nom": "Ketchup", "prix": 70, "image": "assets/images/produits/sauces/ketchup.png", "type": "produit" },
|
||||
{ "id": 57, "nom": "Chinoise", "prix": 70, "image": "assets/images/produits/sauces/sauce-chinoise.png", "type": "produit" },
|
||||
{ "id": 58, "nom": "Curry", "prix": 70, "image": "assets/images/produits/sauces/sauce-curry.png", "type": "produit" },
|
||||
{ "id": 59, "nom": "Pommes Frites", "prix": 70, "image": "assets/images/produits/sauces/sauce-pommes-frite.png", "type": "produit" }
|
||||
],
|
||||
"salades": [
|
||||
{ "id": 60, "nom": "Petite Salade", "prix": 330, "image": "assets/images/produits/salades/petite-salade.png", "type": "produit" },
|
||||
{ "id": 61, "nom": "Cesar Classic", "prix": 880, "image": "assets/images/produits/salades/salade-classic-caesar.png","type": "produit" },
|
||||
{ "id": 62, "nom": "Italienne Mozza", "prix": 880, "image": "assets/images/produits/salades/salade-italian-mozza.png", "type": "produit" }
|
||||
],
|
||||
"wraps": [
|
||||
{ "id": 63, "nom": "MC Wrap Chevre", "prix": 310, "image": "assets/images/produits/wraps/mcwrap-chevre.png", "type": "produit" },
|
||||
{ "id": 64, "nom": "MC Wrap Poulet Bacon", "prix": 330, "image": "assets/images/produits/wraps/mcwrap-poulet-bacon.png","type": "produit" },
|
||||
{ "id": 65, "nom": "Ptit Wrap Chevre", "prix": 260, "image": "assets/images/produits/wraps/ptit-wrap-chevre.png", "type": "produit" },
|
||||
{ "id": 66, "nom": "Ptit Wrap Ranch", "prix": 260, "image": "assets/images/produits/wraps/ptit-wrap-ranch.png", "type": "produit" }
|
||||
]
|
||||
}
|
||||
|
|
@ -3,79 +3,18 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!--
|
||||
noindex: this kiosk is not a public website; no SEO indexing needed.
|
||||
nofollow: prevents link-following by bots reaching the kiosk URL.
|
||||
-->
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="description" content="Borne de commande Wakdo - choisissez votre mode de consommation">
|
||||
<title>Wakdo - Bienvenue</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
<title>Wakdo - borne client</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; margin: 2rem; color: #222; }
|
||||
img { max-height: 80px; }
|
||||
small { color: #666; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!--
|
||||
Welcome / landing screen.
|
||||
Matches maquette page 1: background photo, white card, two choice buttons.
|
||||
Navigation is purely HTML <a> links — no JS required for this screen.
|
||||
-->
|
||||
<main class="welcome" aria-label="Ecran d'accueil Wakdo">
|
||||
|
||||
<!--
|
||||
mc-landing-banner.png is the background photo (M arches + food).
|
||||
alt="" because it is purely decorative — the content lives in the card.
|
||||
-->
|
||||
<img
|
||||
class="welcome__bg"
|
||||
src="assets/images/ui/mc-landing-banner.png"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
>
|
||||
|
||||
<section class="welcome__card" aria-labelledby="welcome-heading">
|
||||
<h1 class="welcome__greeting" id="welcome-heading">Bonjour,</h1>
|
||||
<p class="welcome__question">
|
||||
Souhaitez-vous consommer votre menu sur place<br>
|
||||
ou preferez-vous l'emporter ?
|
||||
</p>
|
||||
|
||||
<nav class="welcome__choices" aria-label="Mode de consommation">
|
||||
<!--
|
||||
href passes the choice via query string.
|
||||
The categories page reads it to display the correct mode label.
|
||||
No JS required for the navigation itself.
|
||||
-->
|
||||
<a
|
||||
class="choice-btn"
|
||||
href="categories.html?mode=sur-place"
|
||||
role="button"
|
||||
aria-label="Commander sur place"
|
||||
>
|
||||
<img
|
||||
class="choice-btn__image"
|
||||
src="assets/images/ui/illustration-sur-place.png"
|
||||
alt="Table et chaises - Sur place"
|
||||
>
|
||||
<span class="choice-btn__label">Sur Place</span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="choice-btn"
|
||||
href="categories.html?mode=a-emporter"
|
||||
role="button"
|
||||
aria-label="Commander a emporter"
|
||||
>
|
||||
<img
|
||||
class="choice-btn__image"
|
||||
src="assets/images/ui/illustration-a-emporter.png"
|
||||
alt="Sac a emporter"
|
||||
>
|
||||
<span class="choice-btn__label">A Emporter</span>
|
||||
</a>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<img src="/assets/images/ui/logo.png" alt="Wakdo">
|
||||
<h1>Wakdo - borne client</h1>
|
||||
<p>En construction.</p>
|
||||
<p><small>Phase P1 - conception Merise en cours. Le front borne sera implemente en phase P5.</small></p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,120 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="description" content="Wakdo - Choisissez votre mode de paiement">
|
||||
<title>Wakdo - Paiement</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body class="payment-page">
|
||||
|
||||
<!--
|
||||
payment.html — Payment method selection.
|
||||
MVP: both buttons simulate payment and redirect to confirmation.html.
|
||||
No real payment integration (kiosk demo — out of scope permanently).
|
||||
-->
|
||||
|
||||
<header class="site-header">
|
||||
<a
|
||||
class="site-header__back"
|
||||
href="cart.html"
|
||||
aria-label="Retour au panier"
|
||||
>
|
||||
← Panier
|
||||
</a>
|
||||
<img
|
||||
class="site-header__logo"
|
||||
src="assets/images/ui/logo.png"
|
||||
alt="Wakdo"
|
||||
>
|
||||
<span class="mode-badge site-header__mode" data-mode-badge aria-label="Mode de consommation">Sur place</span>
|
||||
</header>
|
||||
|
||||
<main class="payment-main" aria-label="Choix du mode de paiement">
|
||||
|
||||
<h1 class="payment-main__heading">Comment souhaitez-vous payer ?</h1>
|
||||
|
||||
<!-- Mini order recap injected by inline script using localStorage -->
|
||||
<div class="payment-recap" id="payment-recap" aria-label="Recapitulatif de commande">
|
||||
<!-- Filled by inline module below -->
|
||||
</div>
|
||||
|
||||
<div class="payment-methods" role="group" aria-label="Modes de paiement">
|
||||
|
||||
<!--
|
||||
Carte bancaire — simulates payment for MVP.
|
||||
Both methods redirect to confirmation.html.
|
||||
-->
|
||||
<button
|
||||
class="payment-choice"
|
||||
id="pay-card"
|
||||
type="button"
|
||||
aria-label="Payer par carte bancaire"
|
||||
>
|
||||
<!-- Card SVG icon (inline, no external dependency) -->
|
||||
<svg class="payment-choice__icon" viewBox="0 0 64 64" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="4" y="14" width="56" height="36" rx="6" ry="6" fill="#FFC72C"/>
|
||||
<rect x="4" y="24" width="56" height="8" fill="#1A1A1A"/>
|
||||
<rect x="12" y="36" width="16" height="6" rx="2" fill="#fff"/>
|
||||
</svg>
|
||||
<span class="payment-choice__label">Carte bancaire</span>
|
||||
</button>
|
||||
|
||||
<!-- Especes -->
|
||||
<button
|
||||
class="payment-choice"
|
||||
id="pay-cash"
|
||||
type="button"
|
||||
aria-label="Payer en especes"
|
||||
>
|
||||
<!-- Cash SVG icon (inline) -->
|
||||
<svg class="payment-choice__icon" viewBox="0 0 64 64" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="4" y="20" width="56" height="32" rx="4" ry="4" fill="#FFC72C"/>
|
||||
<circle cx="32" cy="36" r="8" fill="#fff"/>
|
||||
<circle cx="32" cy="36" r="4" fill="#FFC72C"/>
|
||||
<rect x="8" y="12" width="48" height="6" rx="2" fill="#E6A800"/>
|
||||
</svg>
|
||||
<span class="payment-choice__label">Especes</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module">
|
||||
import { getTotalCents, formatPrice, getCart } from './assets/js/state.js';
|
||||
import { getMode } from './assets/js/state.js';
|
||||
|
||||
/* Show mini recap */
|
||||
const recap = document.getElementById('payment-recap');
|
||||
const total = getTotalCents();
|
||||
const items = getCart();
|
||||
const mode = getMode() === 'a-emporter' ? 'A emporter' : 'Sur place';
|
||||
|
||||
if (recap) {
|
||||
if (!items.length) {
|
||||
/* Guard: redirect back to cart if somehow empty */
|
||||
window.location.href = 'cart.html';
|
||||
} else {
|
||||
recap.innerHTML = `
|
||||
<p class="payment-recap__mode">${mode}</p>
|
||||
<p class="payment-recap__items">${items.length} article${items.length > 1 ? 's' : ''}</p>
|
||||
<p class="payment-recap__total">Total : <strong>${formatPrice(total)}</strong></p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
/* Both payment methods redirect to confirmation — MVP simulation */
|
||||
function simulatePay() {
|
||||
window.location.href = 'confirmation.html';
|
||||
}
|
||||
|
||||
document.getElementById('pay-card').addEventListener('click', simulatePay);
|
||||
document.getElementById('pay-cash').addEventListener('click', simulatePay);
|
||||
</script>
|
||||
|
||||
<script type="module" src="assets/js/nav.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="description" content="Wakdo - Detail du produit">
|
||||
<title>Wakdo - Produit</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body class="product-page">
|
||||
|
||||
<!--
|
||||
product.html — Product detail screen.
|
||||
Reads ?id=<int>&category=<slug>.
|
||||
JS (page-product.js) fetches the product, renders detail and handles
|
||||
the "Ajouter au panier" action.
|
||||
Menu composition is shown as a fixed note (MVP: no composition selection).
|
||||
-->
|
||||
|
||||
<header class="site-header">
|
||||
<a
|
||||
id="back-to-products"
|
||||
class="site-header__back"
|
||||
href="products.html"
|
||||
aria-label="Retour a la liste des produits"
|
||||
>
|
||||
← Retour
|
||||
</a>
|
||||
<img
|
||||
class="site-header__logo"
|
||||
src="assets/images/ui/logo.png"
|
||||
alt="Wakdo"
|
||||
>
|
||||
<a
|
||||
class="site-header__cart"
|
||||
href="cart.html"
|
||||
aria-label="Voir le panier"
|
||||
>
|
||||
<span class="cart-icon" aria-hidden="true">🛒</span>
|
||||
<span class="cart-badge" data-cart-count hidden aria-live="polite">0</span>
|
||||
<span class="sr-only">Panier</span>
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<main class="product-main" aria-label="Detail du produit">
|
||||
|
||||
<!-- Error block: hidden unless fetch fails or id invalid -->
|
||||
<p id="product-error" class="product-error" hidden role="alert"></p>
|
||||
|
||||
<!--
|
||||
Container filled by page-product.js.
|
||||
The JS replaces innerHTML once data is ready.
|
||||
-->
|
||||
<div id="product-detail" class="product-detail" aria-live="polite">
|
||||
<!-- Skeleton placeholder visible during fetch -->
|
||||
<div class="product-detail__skeleton" aria-hidden="true"></div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module" src="assets/js/nav.js"></script>
|
||||
<script type="module" src="assets/js/page-product.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="description" content="Wakdo - Produits de la categorie selectionnee">
|
||||
<title>Wakdo - Produits</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body class="products-page">
|
||||
|
||||
<!--
|
||||
products.html — List of products in a category.
|
||||
Category is determined at runtime from ?category=<id>.
|
||||
JS (page-products.js) fetches data/produits.json and renders cards.
|
||||
In P4: swap fetch URL in data.js to point to GET /api/products?category=<slug>.
|
||||
-->
|
||||
|
||||
<header class="site-header">
|
||||
<a
|
||||
id="back-to-categories"
|
||||
class="site-header__back"
|
||||
href="categories.html"
|
||||
aria-label="Retour aux categories"
|
||||
>
|
||||
← Categories
|
||||
</a>
|
||||
<img
|
||||
class="site-header__logo"
|
||||
src="assets/images/ui/logo.png"
|
||||
alt="Wakdo"
|
||||
>
|
||||
<a
|
||||
class="site-header__cart"
|
||||
href="cart.html"
|
||||
aria-label="Voir le panier"
|
||||
>
|
||||
<span class="cart-icon" aria-hidden="true">🛒</span>
|
||||
<span class="cart-badge" data-cart-count hidden aria-live="polite">0</span>
|
||||
<span class="sr-only">Panier</span>
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<main class="products-main" aria-label="Liste des produits">
|
||||
|
||||
<div class="products-header">
|
||||
<!--
|
||||
Heading is updated by page-products.js once the category
|
||||
data is loaded — default text shown during load.
|
||||
-->
|
||||
<h1 id="products-heading" class="products-main__heading">Nos produits</h1>
|
||||
<span class="mode-badge" data-mode-badge aria-label="Mode de consommation">Sur place</span>
|
||||
</div>
|
||||
|
||||
<!-- Error block: hidden by default, shown if fetch fails -->
|
||||
<p id="products-error" class="products-error" hidden role="alert"></p>
|
||||
|
||||
<ul id="products-grid" class="products-grid" aria-label="Grille de produits">
|
||||
<!-- Product cards injected by page-products.js -->
|
||||
</ul>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module" src="assets/js/nav.js"></script>
|
||||
<script type="module" src="assets/js/page-products.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue