| | <!DOCTYPE html> |
| | <html lang="fr"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>StockMaster - Gestion de Stock</title> |
| | <script src="https://cdn.tailwindcss.com"></script> |
| | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| | <style> |
| | |
| | @keyframes slideIn { |
| | from { transform: translateX(100%); } |
| | to { transform: translateX(0); } |
| | } |
| | |
| | @keyframes fadeOut { |
| | from { opacity: 1; } |
| | to { opacity: 0; } |
| | } |
| | |
| | .notification { |
| | animation: slideIn 0.3s ease-out; |
| | } |
| | |
| | .notification.hide { |
| | animation: fadeOut 0.5s ease-out forwards; |
| | } |
| | |
| | |
| | .table-container { |
| | max-height: 500px; |
| | overflow-y: auto; |
| | } |
| | |
| | .table-container::-webkit-scrollbar { |
| | width: 8px; |
| | } |
| | |
| | .table-container::-webkit-scrollbar-track { |
| | background: #f1f1f1; |
| | } |
| | |
| | .table-container::-webkit-scrollbar-thumb { |
| | background: #888; |
| | border-radius: 4px; |
| | } |
| | |
| | .table-container::-webkit-scrollbar-thumb:hover { |
| | background: #555; |
| | } |
| | |
| | |
| | .tab-content { |
| | display: none; |
| | } |
| | |
| | .tab-content.active { |
| | display: block; |
| | } |
| | |
| | .tab-button { |
| | position: relative; |
| | } |
| | |
| | .tab-button.active::after { |
| | content: ''; |
| | position: absolute; |
| | bottom: -1px; |
| | left: 0; |
| | width: 100%; |
| | height: 2px; |
| | background-color: #3b82f6; |
| | } |
| | |
| | |
| | .status-badge { |
| | font-size: 0.75rem; |
| | padding: 0.25rem 0.5rem; |
| | border-radius: 9999px; |
| | } |
| | |
| | .status-active { |
| | background-color: #d1fae5; |
| | color: #065f46; |
| | } |
| | |
| | .status-inactive { |
| | background-color: #fee2e2; |
| | color: #b91c1c; |
| | } |
| | </style> |
| | </head> |
| | <body class="bg-gray-100 font-sans"> |
| | |
| | <nav class="bg-blue-600 text-white shadow-lg"> |
| | <div class="container mx-auto px-4 py-3 flex justify-between items-center"> |
| | <div class="flex items-center space-x-2"> |
| | <i class="fas fa-boxes text-2xl"></i> |
| | <h1 class="text-2xl font-bold">StockMaster</h1> |
| | </div> |
| | <div class="flex items-center space-x-4"> |
| | <div class="relative"> |
| | <input type="text" id="global-search" placeholder="Rechercher..." |
| | class="px-4 py-2 rounded-full text-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-300"> |
| | <button class="absolute right-3 top-2 text-gray-500"> |
| | <i class="fas fa-search"></i> |
| | </button> |
| | </div> |
| | <button id="dark-mode-toggle" class="p-2 rounded-full hover:bg-blue-700 transition"> |
| | <i class="fas fa-moon"></i> |
| | </button> |
| | <div class="relative group"> |
| | <button class="flex items-center space-x-1 p-2 rounded-full hover:bg-blue-700 transition"> |
| | <i class="fas fa-user-circle text-xl"></i> |
| | </button> |
| | <div class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50 hidden group-hover:block"> |
| | <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Mon profil</a> |
| | <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Paramètres</a> |
| | <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Déconnexion</a> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </nav> |
| |
|
| | |
| | <div class="container mx-auto px-4 py-6"> |
| | |
| | <div class="flex border-b mb-6"> |
| | <button class="tab-button px-4 py-2 font-medium text-gray-600 hover:text-blue-600 transition active" data-tab="products"> |
| | <i class="fas fa-boxes mr-2"></i>Produits |
| | </button> |
| | <button class="tab-button px-4 py-2 font-medium text-gray-600 hover:text-blue-600 transition" data-tab="categories"> |
| | <i class="fas fa-tags mr-2"></i>Catégories |
| | </button> |
| | <button class="tab-button px-4 py-2 font-medium text-gray-600 hover:text-blue-600 transition" data-tab="users"> |
| | <i class="fas fa-users mr-2"></i>Utilisateurs |
| | </button> |
| | <button class="tab-button px-4 py-2 font-medium text-gray-600 hover:text-blue-600 transition" data-tab="attributes"> |
| | <i class="fas fa-list mr-2"></i>Attributs |
| | </button> |
| | </div> |
| |
|
| | |
| | <div id="tab-contents"> |
| | |
| | <div id="products-tab" class="tab-content active"> |
| | |
| | <div class="flex justify-between items-center mb-6"> |
| | <h2 class="text-2xl font-semibold text-gray-800">Gestion du Stock</h2> |
| | <div class="flex space-x-3"> |
| | <button id="add-product-btn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition"> |
| | <i class="fas fa-plus"></i> |
| | <span>Ajouter un produit</span> |
| | </button> |
| | <button id="export-btn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition"> |
| | <i class="fas fa-file-export"></i> |
| | <span>Exporter</span> |
| | </button> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6"> |
| | <div class="bg-white p-4 rounded-lg shadow flex items-center justify-between"> |
| | <div> |
| | <p class="text-gray-500">Produits en stock</p> |
| | <h3 class="text-2xl font-bold" id="total-products">0</h3> |
| | </div> |
| | <div class="bg-blue-100 p-3 rounded-full"> |
| | <i class="fas fa-boxes text-blue-500 text-xl"></i> |
| | </div> |
| | </div> |
| | <div class="bg-white p-4 rounded-lg shadow flex items-center justify-between"> |
| | <div> |
| | <p class="text-gray-500">Valeur totale</p> |
| | <h3 class="text-2xl font-bold" id="total-value">0 €</h3> |
| | </div> |
| | <div class="bg-green-100 p-3 rounded-full"> |
| | <i class="fas fa-euro-sign text-green-500 text-xl"></i> |
| | </div> |
| | </div> |
| | <div class="bg-white p-4 rounded-lg shadow flex items-center justify-between"> |
| | <div> |
| | <p class="text-gray-500">Stock faible</p> |
| | <h3 class="text-2xl font-bold" id="low-stock">0</h3> |
| | </div> |
| | <div class="bg-yellow-100 p-3 rounded-full"> |
| | <i class="fas fa-exclamation-triangle text-yellow-500 text-xl"></i> |
| | </div> |
| | </div> |
| | <div class="bg-white p-4 rounded-lg shadow flex items-center justify-between"> |
| | <div> |
| | <p class="text-gray-500">Produits épuisés</p> |
| | <h3 class="text-2xl font-bold" id="out-of-stock">0</h3> |
| | </div> |
| | <div class="bg-red-100 p-3 rounded-full"> |
| | <i class="fas fa-times-circle text-red-500 text-xl"></i> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="bg-white rounded-lg shadow overflow-hidden"> |
| | <div class="p-4 border-b flex justify-between items-center"> |
| | <div class="flex items-center space-x-4"> |
| | <h3 class="font-semibold text-lg">Liste des produits</h3> |
| | <div class="relative"> |
| | <select id="filter-category" class="appearance-none bg-gray-100 border border-gray-300 rounded px-3 py-1 pr-8 focus:outline-none focus:ring-2 focus:ring-blue-300"> |
| | <option value="all">Toutes catégories</option> |
| | |
| | </select> |
| | <div class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> |
| | <i class="fas fa-chevron-down text-gray-500"></i> |
| | </div> |
| | </div> |
| | </div> |
| | <div class="flex items-center space-x-2"> |
| | <div class="relative"> |
| | <input type="text" id="search-products" placeholder="Rechercher..." |
| | class="px-3 py-1 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300"> |
| | <button class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500"> |
| | <i class="fas fa-search"></i> |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| | <div class="table-container"> |
| | <table class="min-w-full divide-y divide-gray-200"> |
| | <thead class="bg-gray-50"> |
| | <tr> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nom</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Catégorie</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Quantité</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Prix unitaire</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Valeur</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> |
| | </tr> |
| | </thead> |
| | <tbody id="products-table-body" class="bg-white divide-y divide-gray-200"> |
| | |
| | </tbody> |
| | </table> |
| | </div> |
| | <div class="p-4 border-t flex justify-between items-center"> |
| | <div class="text-sm text-gray-500"> |
| | Affichage de <span id="displayed-items">0</span> sur <span id="total-items">0</span> produits |
| | </div> |
| | <div class="flex space-x-2"> |
| | <button id="prev-page" class="px-3 py-1 border rounded disabled:opacity-50" disabled> |
| | <i class="fas fa-chevron-left"></i> |
| | </button> |
| | <button id="next-page" class="px-3 py-1 border rounded disabled:opacity-50" disabled> |
| | <i class="fas fa-chevron-right"></i> |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="categories-tab" class="tab-content"> |
| | <div class="flex justify-between items-center mb-6"> |
| | <h2 class="text-2xl font-semibold text-gray-800">Gestion des Catégories</h2> |
| | <button id="add-category-btn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition"> |
| | <i class="fas fa-plus"></i> |
| | <span>Ajouter une catégorie</span> |
| | </button> |
| | </div> |
| |
|
| | |
| | <div class="bg-white rounded-lg shadow overflow-hidden"> |
| | <div class="p-4 border-b flex justify-between items-center"> |
| | <h3 class="font-semibold text-lg">Liste des catégories</h3> |
| | <div class="relative"> |
| | <input type="text" id="search-categories" placeholder="Rechercher..." |
| | class="px-3 py-1 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300"> |
| | <button class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500"> |
| | <i class="fas fa-search"></i> |
| | </button> |
| | </div> |
| | </div> |
| | <div class="table-container"> |
| | <table class="min-w-full divide-y divide-gray-200"> |
| | <thead class="bg-gray-50"> |
| | <tr> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nom</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Statut</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Produits</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> |
| | </tr> |
| | </thead> |
| | <tbody id="categories-table-body" class="bg-white divide-y divide-gray-200"> |
| | |
| | </tbody> |
| | </table> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="users-tab" class="tab-content"> |
| | <div class="flex justify-between items-center mb-6"> |
| | <h2 class="text-2xl font-semibold text-gray-800">Gestion des Utilisateurs</h2> |
| | <button id="add-user-btn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition"> |
| | <i class="fas fa-plus"></i> |
| | <span>Ajouter un utilisateur</span> |
| | </button> |
| | </div> |
| |
|
| | |
| | <div class="bg-white rounded-lg shadow overflow-hidden"> |
| | <div class="p-4 border-b flex justify-between items-center"> |
| | <h3 class="font-semibold text-lg">Liste des utilisateurs</h3> |
| | <div class="relative"> |
| | <input type="text" id="search-users" placeholder="Rechercher..." |
| | class="px-3 py-1 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300"> |
| | <button class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500"> |
| | <i class="fas fa-search"></i> |
| | </button> |
| | </div> |
| | </div> |
| | <div class="table-container"> |
| | <table class="min-w-full divide-y divide-gray-200"> |
| | <thead class="bg-gray-50"> |
| | <tr> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nom</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Rôle</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Statut</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> |
| | </tr> |
| | </thead> |
| | <tbody id="users-table-body" class="bg-white divide-y divide-gray-200"> |
| | |
| | </tbody> |
| | </table> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="attributes-tab" class="tab-content"> |
| | <div class="flex justify-between items-center mb-6"> |
| | <h2 class="text-2xl font-semibold text-gray-800">Gestion des Attributs</h2> |
| | <button id="add-attribute-btn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition"> |
| | <i class="fas fa-plus"></i> |
| | <span>Ajouter un attribut</span> |
| | </button> |
| | </div> |
| |
|
| | |
| | <div class="bg-white rounded-lg shadow overflow-hidden"> |
| | <div class="p-4 border-b flex justify-between items-center"> |
| | <h3 class="font-semibold text-lg">Liste des attributs</h3> |
| | <div class="relative"> |
| | <input type="text" id="search-attributes" placeholder="Rechercher..." |
| | class="px-3 py-1 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300"> |
| | <button class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500"> |
| | <i class="fas fa-search"></i> |
| | </button> |
| | </div> |
| | </div> |
| | <div class="table-container"> |
| | <table class="min-w-full divide-y divide-gray-200"> |
| | <thead class="bg-gray-50"> |
| | <tr> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nom</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Valeurs</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Catégories</th> |
| | <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> |
| | </tr> |
| | </thead> |
| | <tbody id="attributes-table-body" class="bg-white divide-y divide-gray-200"> |
| | |
| | </tbody> |
| | </table> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="product-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
| | <div class="bg-white rounded-lg shadow-xl w-full max-w-md"> |
| | <div class="p-4 border-b flex justify-between items-center"> |
| | <h3 class="text-lg font-semibold" id="modal-product-title">Ajouter un produit</h3> |
| | <button class="close-modal text-gray-500 hover:text-gray-700"> |
| | <i class="fas fa-times"></i> |
| | </button> |
| | </div> |
| | <div class="p-4"> |
| | <form id="product-form"> |
| | <input type="hidden" id="product-id"> |
| | <div class="mb-4"> |
| | <label for="product-name" class="block text-gray-700 mb-2">Nom du produit</label> |
| | <input type="text" id="product-name" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | </div> |
| | <div class="mb-4"> |
| | <label for="product-category" class="block text-gray-700 mb-2">Catégorie</label> |
| | <select id="product-category" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | <option value="">Sélectionner une catégorie</option> |
| | |
| | </select> |
| | </div> |
| | <div class="grid grid-cols-2 gap-4 mb-4"> |
| | <div> |
| | <label for="product-quantity" class="block text-gray-700 mb-2">Quantité</label> |
| | <input type="number" id="product-quantity" min="0" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | </div> |
| | <div> |
| | <label for="product-price" class="block text-gray-700 mb-2">Prix unitaire (€)</label> |
| | <input type="number" id="product-price" min="0" step="0.01" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | </div> |
| | </div> |
| | <div class="mb-4"> |
| | <label for="product-description" class="block text-gray-700 mb-2">Description</label> |
| | <textarea id="product-description" rows="3" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300"></textarea> |
| | </div> |
| | <div class="mb-4" id="product-attributes-container"> |
| | |
| | </div> |
| | </form> |
| | </div> |
| | <div class="p-4 border-t flex justify-end space-x-3"> |
| | <button class="cancel-modal px-4 py-2 border rounded-lg text-gray-700 hover:bg-gray-100 transition"> |
| | Annuler |
| | </button> |
| | <button id="save-product" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition"> |
| | Enregistrer |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="category-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
| | <div class="bg-white rounded-lg shadow-xl w-full max-w-md"> |
| | <div class="p-4 border-b flex justify-between items-center"> |
| | <h3 class="text-lg font-semibold" id="modal-category-title">Ajouter une catégorie</h3> |
| | <button class="close-modal text-gray-500 hover:text-gray-700"> |
| | <i class="fas fa-times"></i> |
| | </button> |
| | </div> |
| | <div class="p-4"> |
| | <form id="category-form"> |
| | <input type="hidden" id="category-id"> |
| | <div class="mb-4"> |
| | <label for="category-name" class="block text-gray-700 mb-2">Nom de la catégorie</label> |
| | <input type="text" id="category-name" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | </div> |
| | <div class="mb-4"> |
| | <label for="category-description" class="block text-gray-700 mb-2">Description</label> |
| | <textarea id="category-description" rows="3" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300"></textarea> |
| | </div> |
| | <div class="mb-4"> |
| | <label for="category-status" class="block text-gray-700 mb-2">Statut</label> |
| | <select id="category-status" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | <option value="active">Active</option> |
| | <option value="inactive">Inactive</option> |
| | </select> |
| | </div> |
| | <div class="mb-4"> |
| | <label class="block text-gray-700 mb-2">Attributs associés</label> |
| | <div id="category-attributes-container" class="space-y-2"> |
| | |
| | </div> |
| | </div> |
| | </form> |
| | </div> |
| | <div class="p-4 border-t flex justify-end space-x-3"> |
| | <button class="cancel-modal px-4 py-2 border rounded-lg text-gray-700 hover:bg-gray-100 transition"> |
| | Annuler |
| | </button> |
| | <button id="save-category" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition"> |
| | Enregistrer |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="user-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
| | <div class="bg-white rounded-lg shadow-xl w-full max-w-md"> |
| | <div class="p-4 border-b flex justify-between items-center"> |
| | <h3 class="text-lg font-semibold" id="modal-user-title">Ajouter un utilisateur</h3> |
| | <button class="close-modal text-gray-500 hover:text-gray-700"> |
| | <i class="fas fa-times"></i> |
| | </button> |
| | </div> |
| | <div class="p-4"> |
| | <form id="user-form"> |
| | <input type="hidden" id="user-id"> |
| | <div class="mb-4"> |
| | <label for="user-firstname" class="block text-gray-700 mb-2">Prénom</label> |
| | <input type="text" id="user-firstname" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | </div> |
| | <div class="mb-4"> |
| | <label for="user-lastname" class="block text-gray-700 mb-2">Nom</label> |
| | <input type="text" id="user-lastname" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | </div> |
| | <div class="mb-4"> |
| | <label for="user-email" class="block text-gray-700 mb-2">Email</label> |
| | <input type="email" id="user-email" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | </div> |
| | <div class="mb-4"> |
| | <label for="user-role" class="block text-gray-700 mb-2">Rôle</label> |
| | <select id="user-role" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | <option value="admin">Administrateur</option> |
| | <option value="manager">Gestionnaire</option> |
| | <option value="user">Utilisateur</option> |
| | </select> |
| | </div> |
| | <div class="mb-4"> |
| | <label for="user-status" class="block text-gray-700 mb-2">Statut</label> |
| | <select id="user-status" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | <option value="active">Actif</option> |
| | <option value="inactive">Inactif</option> |
| | </select> |
| | </div> |
| | <div class="mb-4" id="user-password-container"> |
| | <label for="user-password" class="block text-gray-700 mb-2">Mot de passe</label> |
| | <input type="password" id="user-password" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300"> |
| | <p class="text-xs text-gray-500 mt-1">Laissez vide pour ne pas modifier</p> |
| | </div> |
| | </form> |
| | </div> |
| | <div class="p-4 border-t flex justify-end space-x-3"> |
| | <button class="cancel-modal px-4 py-2 border rounded-lg text-gray-700 hover:bg-gray-100 transition"> |
| | Annuler |
| | </button> |
| | <button id="save-user" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition"> |
| | Enregistrer |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="attribute-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
| | <div class="bg-white rounded-lg shadow-xl w-full max-w-md"> |
| | <div class="p-4 border-b flex justify-between items-center"> |
| | <h3 class="text-lg font-semibold" id="modal-attribute-title">Ajouter un attribut</h3> |
| | <button class="close-modal text-gray-500 hover:text-gray-700"> |
| | <i class="fas fa-times"></i> |
| | </button> |
| | </div> |
| | <div class="p-4"> |
| | <form id="attribute-form"> |
| | <input type="hidden" id="attribute-id"> |
| | <div class="mb-4"> |
| | <label for="attribute-name" class="block text-gray-700 mb-2">Nom de l'attribut</label> |
| | <input type="text" id="attribute-name" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | </div> |
| | <div class="mb-4"> |
| | <label for="attribute-type" class="block text-gray-700 mb-2">Type</label> |
| | <select id="attribute-type" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300" required> |
| | <option value="text">Texte</option> |
| | <option value="number">Nombre</option> |
| | <option value="select">Liste déroulante</option> |
| | <option value="checkbox">Case à cocher</option> |
| | </select> |
| | </div> |
| | <div class="mb-4 hidden" id="attribute-values-container"> |
| | <label for="attribute-values" class="block text-gray-700 mb-2">Valeurs possibles (séparées par des virgules)</label> |
| | <textarea id="attribute-values" rows="2" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300"></textarea> |
| | <p class="text-xs text-gray-500 mt-1">Exemple: Rouge,Bleu,Vert</p> |
| | </div> |
| | <div class="mb-4"> |
| | <label class="block text-gray-700 mb-2">Catégories associées</label> |
| | <div id="attribute-categories-container" class="space-y-2"> |
| | |
| | </div> |
| | </div> |
| | </form> |
| | </div> |
| | <div class="p-4 border-t flex justify-end space-x-3"> |
| | <button class="cancel-modal px-4 py-2 border rounded-lg text-gray-700 hover:bg-gray-100 transition"> |
| | Annuler |
| | </button> |
| | <button id="save-attribute" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition"> |
| | Enregistrer |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="notifications" class="fixed bottom-4 right-4 space-y-2 z-50"></div> |
| |
|
| | <script> |
| | |
| | let products = JSON.parse(localStorage.getItem('products')) || [ |
| | { id: 1, name: "Ordinateur portable", category: "Electronique", quantity: 15, price: 899.99, description: "PC portable 15 pouces, 16GB RAM, SSD 512GB" }, |
| | { id: 2, name: "Smartphone", category: "Electronique", quantity: 32, price: 699.50, description: "Smartphone haut de gamme avec écran AMOLED" }, |
| | { id: 3, name: "T-shirt", category: "Vêtements", quantity: 45, price: 19.99, description: "T-shirt en coton bio" }, |
| | { id: 4, name: "Chocolat noir", category: "Alimentaire", quantity: 8, price: 3.50, description: "Tablette de chocolat noir 70%" }, |
| | { id: 5, name: "Chaise de bureau", category: "Meubles", quantity: 12, price: 129.99, description: "Chaise ergonomique pour bureau" } |
| | ]; |
| | |
| | let categories = JSON.parse(localStorage.getItem('categories')) || [ |
| | { id: 1, name: "Electronique", description: "Appareils électroniques et informatiques", status: "active", attributes: [1, 2] }, |
| | { id: 2, name: "Alimentaire", description: "Produits alimentaires et consommables", status: "active", attributes: [3] }, |
| | { id: 3, name: "Vêtements", description: "Vêtements et accessoires", status: "active", attributes: [4, 5] }, |
| | { id: 4, name: "Meubles", description: "Meubles et décoration", status: "active", attributes: [6] } |
| | ]; |
| | |
| | let users = JSON.parse(localStorage.getItem('users')) || [ |
| | { id: 1, firstname: "Admin", lastname: "System", email: "admin@stockmaster.com", role: "admin", status: "active", password: "admin123" }, |
| | { id: 2, firstname: "Manager", lastname: "Stock", email: "manager@stockmaster.com", role: "manager", status: "active", password: "manager123" }, |
| | { id: 3, firstname: "Utilisateur", lastname: "Test", email: "user@stockmaster.com", role: "user", status: "active", password: "user123" } |
| | ]; |
| | |
| | let attributes = JSON.parse(localStorage.getItem('attributes')) || [ |
| | { id: 1, name: "Marque", type: "select", values: "Apple,Samsung,Sony,LG", categories: [1] }, |
| | { id: 2, name: "Couleur", type: "select", values: "Noir,Blanc,Gris,Bleu,Rouge", categories: [1, 3] }, |
| | { id: 3, name: "Poids", type: "number", values: "", categories: [2] }, |
| | { id: 4, name: "Taille", type: "select", values: "XS,S,M,L,XL,XXL", categories: [3] }, |
| | { id: 5, name: "Matériau", type: "select", values: "Coton,Polyester,Laine,Soie", categories: [3] }, |
| | { id: 6, name: "Dimensions", type: "text", values: "", categories: [4] } |
| | ]; |
| | |
| | |
| | const productsTableBody = document.getElementById('products-table-body'); |
| | const categoriesTableBody = document.getElementById('categories-table-body'); |
| | const usersTableBody = document.getElementById('users-table-body'); |
| | const attributesTableBody = document.getElementById('attributes-table-body'); |
| | |
| | const productForm = document.getElementById('product-form'); |
| | const categoryForm = document.getElementById('category-form'); |
| | const userForm = document.getElementById('user-form'); |
| | const attributeForm = document.getElementById('attribute-form'); |
| | |
| | const productModal = document.getElementById('product-modal'); |
| | const categoryModal = document.getElementById('category-modal'); |
| | const userModal = document.getElementById('user-modal'); |
| | const attributeModal = document.getElementById('attribute-modal'); |
| | |
| | const modalProductTitle = document.getElementById('modal-product-title'); |
| | const modalCategoryTitle = document.getElementById('modal-category-title'); |
| | const modalUserTitle = document.getElementById('modal-user-title'); |
| | const modalAttributeTitle = document.getElementById('modal-attribute-title'); |
| | |
| | const addProductBtn = document.getElementById('add-product-btn'); |
| | const addCategoryBtn = document.getElementById('add-category-btn'); |
| | const addUserBtn = document.getElementById('add-user-btn'); |
| | const addAttributeBtn = document.getElementById('add-attribute-btn'); |
| | |
| | const saveProductBtn = document.getElementById('save-product'); |
| | const saveCategoryBtn = document.getElementById('save-category'); |
| | const saveUserBtn = document.getElementById('save-user'); |
| | const saveAttributeBtn = document.getElementById('save-attribute'); |
| | |
| | const searchProductsInput = document.getElementById('search-products'); |
| | const searchCategoriesInput = document.getElementById('search-categories'); |
| | const searchUsersInput = document.getElementById('search-users'); |
| | const searchAttributesInput = document.getElementById('search-attributes'); |
| | |
| | const filterCategorySelect = document.getElementById('filter-category'); |
| | const darkModeToggle = document.getElementById('dark-mode-toggle'); |
| | const notificationsContainer = document.getElementById('notifications'); |
| | |
| | const totalProductsElement = document.getElementById('total-products'); |
| | const totalValueElement = document.getElementById('total-value'); |
| | const lowStockElement = document.getElementById('low-stock'); |
| | const outOfStockElement = document.getElementById('out-of-stock'); |
| | const displayedItemsElement = document.getElementById('displayed-items'); |
| | const totalItemsElement = document.getElementById('total-items'); |
| | |
| | const prevPageBtn = document.getElementById('prev-page'); |
| | const nextPageBtn = document.getElementById('next-page'); |
| | const exportBtn = document.getElementById('export-btn'); |
| | |
| | const productAttributesContainer = document.getElementById('product-attributes-container'); |
| | const categoryAttributesContainer = document.getElementById('category-attributes-container'); |
| | const attributeValuesContainer = document.getElementById('attribute-values-container'); |
| | const attributeCategoriesContainer = document.getElementById('attribute-categories-container'); |
| | const attributeTypeSelect = document.getElementById('attribute-type'); |
| | |
| | |
| | let currentPage = 1; |
| | const itemsPerPage = 5; |
| | let filteredProducts = []; |
| | |
| | |
| | const tabButtons = document.querySelectorAll('.tab-button'); |
| | const tabContents = document.querySelectorAll('.tab-content'); |
| | |
| | |
| | document.addEventListener('DOMContentLoaded', () => { |
| | updateStatistics(); |
| | renderProductsTable(); |
| | renderCategoriesTable(); |
| | renderUsersTable(); |
| | renderAttributesTable(); |
| | populateCategoryFilter(); |
| | |
| | |
| | if (localStorage.getItem('darkMode') === 'enabled') { |
| | document.body.classList.add('dark'); |
| | darkModeToggle.innerHTML = '<i class="fas fa-sun"></i>'; |
| | } |
| | }); |
| | |
| | |
| | function showNotification(message, type = 'success') { |
| | const notification = document.createElement('div'); |
| | notification.className = `notification bg-${type === 'success' ? 'green' : type === 'error' ? 'red' : 'blue'}-500 text-white px-4 py-3 rounded-lg shadow-lg flex items-center justify-between`; |
| | |
| | notification.innerHTML = ` |
| | <span>${message}</span> |
| | <button class="close-notification ml-4"> |
| | <i class="fas fa-times"></i> |
| | </button> |
| | `; |
| | |
| | notificationsContainer.appendChild(notification); |
| | |
| | |
| | setTimeout(() => { |
| | notification.classList.add('hide'); |
| | setTimeout(() => notification.remove(), 500); |
| | }, 5000); |
| | |
| | |
| | notification.querySelector('.close-notification').addEventListener('click', () => { |
| | notification.classList.add('hide'); |
| | setTimeout(() => notification.remove(), 500); |
| | }); |
| | } |
| | |
| | |
| | function updateStatistics() { |
| | const totalProducts = products.length; |
| | const totalValue = products.reduce((sum, product) => sum + (product.quantity * product.price), 0); |
| | const lowStock = products.filter(product => product.quantity > 0 && product.quantity <= 5).length; |
| | const outOfStock = products.filter(product => product.quantity === 0).length; |
| | |
| | totalProductsElement.textContent = totalProducts; |
| | totalValueElement.textContent = totalValue.toFixed(2) + ' €'; |
| | lowStockElement.textContent = lowStock; |
| | outOfStockElement.textContent = outOfStock; |
| | |
| | |
| | localStorage.setItem('products', JSON.stringify(products)); |
| | localStorage.setItem('categories', JSON.stringify(categories)); |
| | localStorage.setItem('users', JSON.stringify(users)); |
| | localStorage.setItem('attributes', JSON.stringify(attributes)); |
| | } |
| | |
| | |
| | function populateCategoryFilter() { |
| | filterCategorySelect.innerHTML = '<option value="all">Toutes catégories</option>'; |
| | categories.forEach(category => { |
| | if (category.status === 'active') { |
| | const option = document.createElement('option'); |
| | option.value = category.name; |
| | option.textContent = category.name; |
| | filterCategorySelect.appendChild(option); |
| | } |
| | }); |
| | |
| | |
| | const productCategorySelect = document.getElementById('product-category'); |
| | productCategorySelect.innerHTML = '<option value="">Sélectionner une catégorie</option>'; |
| | categories.forEach(category => { |
| | if (category.status === 'active') { |
| | const option = document.createElement('option'); |
| | option.value = category.name; |
| | option.textContent = category.name; |
| | productCategorySelect.appendChild(option); |
| | } |
| | }); |
| | } |
| | |
| | |
| | function renderProductsTable(productsToRender = products) { |
| | filteredProducts = productsToRender; |
| | totalItemsElement.textContent = filteredProducts.length; |
| | |
| | |
| | const startIndex = (currentPage - 1) * itemsPerPage; |
| | const endIndex = startIndex + itemsPerPage; |
| | const paginatedProducts = filteredProducts.slice(startIndex, endIndex); |
| | |
| | displayedItemsElement.textContent = paginatedProducts.length; |
| | |
| | |
| | prevPageBtn.disabled = currentPage === 1; |
| | nextPageBtn.disabled = endIndex >= filteredProducts.length; |
| | |
| | productsTableBody.innerHTML = ''; |
| | |
| | if (paginatedProducts.length === 0) { |
| | productsTableBody.innerHTML = ` |
| | <tr> |
| | <td colspan="7" class="px-6 py-4 text-center text-gray-500"> |
| | Aucun produit trouvé. Ajoutez un nouveau produit en cliquant sur le bouton "Ajouter un produit". |
| | </td> |
| | </tr> |
| | `; |
| | return; |
| | } |
| | |
| | paginatedProducts.forEach(product => { |
| | const row = document.createElement('tr'); |
| | const value = product.quantity * product.price; |
| | const quantityClass = product.quantity === 0 ? 'bg-red-100 text-red-800' : |
| | product.quantity <= 5 ? 'bg-yellow-100 text-yellow-800' : ''; |
| | |
| | row.innerHTML = ` |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${product.id}</td> |
| | <td class="px-6 py-4 whitespace-nowrap"> |
| | <div class="text-sm font-medium text-gray-900">${product.name}</div> |
| | <div class="text-sm text-gray-500 truncate max-w-xs">${product.description || 'Aucune description'}</div> |
| | </td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> |
| | <span class="px-2 py-1 bg-blue-100 text-blue-800 rounded-full text-xs">${product.category}</span> |
| | </td> |
| | <td class="px-6 py-4 whitespace-nowrap"> |
| | <span class="px-2 py-1 rounded-full text-xs font-medium ${quantityClass}">${product.quantity}</span> |
| | </td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${product.price.toFixed(2)} €</td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${value.toFixed(2)} €</td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm font-medium"> |
| | <button class="edit-product text-blue-600 hover:text-blue-900 mr-3" data-id="${product.id}"> |
| | <i class="fas fa-edit"></i> |
| | </button> |
| | <button class="delete-product text-red-600 hover:text-red-900" data-id="${product.id}"> |
| | <i class="fas fa-trash"></i> |
| | </button> |
| | </td> |
| | `; |
| | |
| | productsTableBody.appendChild(row); |
| | }); |
| | |
| | |
| | document.querySelectorAll('.edit-product').forEach(btn => { |
| | btn.addEventListener('click', (e) => { |
| | const productId = parseInt(e.currentTarget.getAttribute('data-id')); |
| | editProduct(productId); |
| | }); |
| | }); |
| | |
| | document.querySelectorAll('.delete-product').forEach(btn => { |
| | btn.addEventListener('click', (e) => { |
| | const productId = parseInt(e.currentTarget.getAttribute('data-id')); |
| | deleteProduct(productId); |
| | }); |
| | }); |
| | } |
| | |
| | |
| | function renderCategoriesTable(categoriesToRender = categories) { |
| | categoriesTableBody.innerHTML = ''; |
| | |
| | if (categoriesToRender.length === 0) { |
| | categoriesTableBody.innerHTML = ` |
| | <tr> |
| | <td colspan="6" class="px-6 py-4 text-center text-gray-500"> |
| | Aucune catégorie trouvée. Ajoutez une nouvelle catégorie en cliquant sur le bouton "Ajouter une catégorie". |
| | </td> |
| | </tr> |
| | `; |
| | return; |
| | } |
| | |
| | categoriesToRender.forEach(category => { |
| | const productsCount = products.filter(p => p.category === category.name).length; |
| | const statusClass = category.status === 'active' ? 'status-active' : 'status-inactive'; |
| | const statusText = category.status === 'active' ? 'Active' : 'Inactive'; |
| | |
| | const row = document.createElement('tr'); |
| | row.innerHTML = ` |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${category.id}</td> |
| | <td class="px-6 py-4 whitespace-nowrap"> |
| | <div class="text-sm font-medium text-gray-900">${category.name}</div> |
| | </td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> |
| | ${category.description || 'Aucune description'} |
| | </td> |
| | <td class="px-6 py-4 whitespace-nowrap"> |
| | <span class="status-badge ${statusClass}">${statusText}</span> |
| | </td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${productsCount}</td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm font-medium"> |
| | <button class="edit-category text-blue-600 hover:text-blue-900 mr-3" data-id="${category.id}"> |
| | <i class="fas fa-edit"></i> |
| | </button> |
| | <button class="delete-category text-red-600 hover:text-red-900" data-id="${category.id}"> |
| | <i class="fas fa-trash"></i> |
| | </button> |
| | </td> |
| | `; |
| | |
| | categoriesTableBody.appendChild(row); |
| | }); |
| | |
| | |
| | document.querySelectorAll('.edit-category').forEach(btn => { |
| | btn.addEventListener('click', (e) => { |
| | const categoryId = parseInt(e.currentTarget.getAttribute('data-id')); |
| | editCategory(categoryId); |
| | }); |
| | }); |
| | |
| | document.querySelectorAll('.delete-category').forEach(btn => { |
| | btn.addEventListener('click', (e) => { |
| | const categoryId = parseInt(e.currentTarget.getAttribute('data-id')); |
| | deleteCategory(categoryId); |
| | }); |
| | }); |
| | } |
| | |
| | |
| | function renderUsersTable(usersToRender = users) { |
| | usersTableBody.innerHTML = ''; |
| | |
| | if (usersToRender.length === 0) { |
| | usersTableBody.innerHTML = ` |
| | <tr> |
| | <td colspan="6" class="px-6 py-4 text-center text-gray-500"> |
| | Aucun utilisateur trouvé. Ajoutez un nouvel utilisateur en cliquant sur le bouton "Ajouter un utilisateur". |
| | </td> |
| | </tr> |
| | `; |
| | return; |
| | } |
| | |
| | usersToRender.forEach(user => { |
| | const statusClass = user.status === 'active' ? 'status-active' : 'status-inactive'; |
| | const statusText = user.status === 'active' ? 'Actif' : 'Inactif'; |
| | const roleText = user.role === 'admin' ? 'Administrateur' : |
| | user.role === 'manager' ? 'Gestionnaire' : 'Utilisateur'; |
| | |
| | const row = document.createElement('tr'); |
| | row.innerHTML = ` |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${user.id}</td> |
| | <td class="px-6 py-4 whitespace-nowrap"> |
| | <div class="text-sm font-medium text-gray-900">${user.firstname} ${user.lastname}</div> |
| | </td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${user.email}</td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${roleText}</td> |
| | <td class="px-6 py-4 whitespace-nowrap"> |
| | <span class="status-badge ${statusClass}">${statusText}</span> |
| | </td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm font-medium"> |
| | <button class="edit-user text-blue-600 hover:text-blue-900 mr-3" data-id="${user.id}"> |
| | <i class="fas fa-edit"></i> |
| | </button> |
| | <button class="delete-user text-red-600 hover:text-red-900" data-id="${user.id}"> |
| | <i class="fas fa-trash"></i> |
| | </button> |
| | </td> |
| | `; |
| | |
| | usersTableBody.appendChild(row); |
| | }); |
| | |
| | |
| | document.querySelectorAll('.edit-user').forEach(btn => { |
| | btn.addEventListener('click', (e) => { |
| | const userId = parseInt(e.currentTarget.getAttribute('data-id')); |
| | editUser(userId); |
| | }); |
| | }); |
| | |
| | document.querySelectorAll('.delete-user').forEach(btn => { |
| | btn.addEventListener('click', (e) => { |
| | const userId = parseInt(e.currentTarget.getAttribute('data-id')); |
| | deleteUser(userId); |
| | }); |
| | }); |
| | } |
| | |
| | |
| | function renderAttributesTable(attributesToRender = attributes) { |
| | attributesTableBody.innerHTML = ''; |
| | |
| | if (attributesToRender.length === 0) { |
| | attributesTableBody.innerHTML = ` |
| | <tr> |
| | <td colspan="6" class="px-6 py-4 text-center text-gray-500"> |
| | Aucun attribut trouvé. Ajoutez un nouvel attribut en cliquant sur le bouton "Ajouter un attribut". |
| | </td> |
| | </tr> |
| | `; |
| | return; |
| | } |
| | |
| | attributesToRender.forEach(attribute => { |
| | const typeText = attribute.type === 'select' ? 'Liste déroulante' : |
| | attribute.type === 'text' ? 'Texte' : |
| | attribute.type === 'number' ? 'Nombre' : 'Case à cocher'; |
| | |
| | const categoryNames = attribute.categories.map(catId => { |
| | const category = categories.find(c => c.id === catId); |
| | return category ? category.name : 'Inconnue'; |
| | }).join(', '); |
| | |
| | const row = document.createElement('tr'); |
| | row.innerHTML = ` |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${attribute.id}</td> |
| | <td class="px-6 py-4 whitespace-nowrap"> |
| | <div class="text-sm font-medium text-gray-900">${attribute.name}</div> |
| | </td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${typeText}</td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> |
| | ${attribute.values || 'Aucune valeur prédéfinie'} |
| | </td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> |
| | ${categoryNames || 'Aucune catégorie'} |
| | </td> |
| | <td class="px-6 py-4 whitespace-nowrap text-sm font-medium"> |
| | <button class="edit-attribute text-blue-600 hover:text-blue-900 mr-3" data-id="${attribute.id}"> |
| | <i class="fas fa-edit"></i> |
| | </button> |
| | <button class="delete-attribute text-red-600 hover:text-red-900" data-id="${attribute.id}"> |
| | <i class="fas fa-trash"></i> |
| | </button> |
| | </td> |
| | `; |
| | |
| | attributesTableBody.appendChild(row); |
| | }); |
| | |
| | |
| | document.querySelectorAll('.edit-attribute').forEach(btn => { |
| | btn.addEventListener('click', (e) => { |
| | const attributeId = parseInt(e.currentTarget.getAttribute('data-id')); |
| | editAttribute(attributeId); |
| | }); |
| | }); |
| | |
| | document.querySelectorAll('.delete-attribute').forEach(btn => { |
| | btn.addEventListener('click', (e) => { |
| | const attributeId = parseInt(e.currentTarget.getAttribute('data-id')); |
| | deleteAttribute(attributeId); |
| | }); |
| | }); |
| | } |
| | |
| | |
| | function addProduct() { |
| | modalProductTitle.textContent = 'Ajouter un produit'; |
| | productForm.reset(); |
| | productModal.classList.remove('hidden'); |
| | |
| | |
| | const newId = products.length > 0 ? Math.max(...products.map(p => p.id)) + 1 : 1; |
| | document.getElementById('product-id').value = newId; |
| | |
| | |
| | updateProductAttributes(); |
| | } |
| | |
| | |
| | function editProduct(id) { |
| | const product = products.find(p => p.id === id); |
| | if (!product) return; |
| | |
| | modalProductTitle.textContent = 'Modifier le produit'; |
| | productForm.reset(); |
| | |
| | document.getElementById('product-id').value = product.id; |
| | document.getElementById('product-name').value = product.name; |
| | document.getElementById('product-category').value = product.category; |
| | document.getElementById('product-quantity').value = product.quantity; |
| | document.getElementById('product-price').value = product.price; |
| | document.getElementById('product-description').value = product.description || ''; |
| | |
| | productModal.classList.remove('hidden'); |
| | |
| | |
| | updateProductAttributes(product.category); |
| | } |
| | |
| | |
| | function updateProductAttributes(categoryName = null) { |
| | productAttributesContainer.innerHTML = ''; |
| | |
| | if (!categoryName) return; |
| | |
| | const category = categories.find(c => c.name === categoryName); |
| | if (!category || !category.attributes || category.attributes.length === 0) return; |
| | |
| | category.attributes.forEach(attrId => { |
| | const attribute = attributes.find(a => a.id === attrId); |
| | if (!attribute) return; |
| | |
| | const div = document.createElement('div'); |
| | div.className = 'mb-4'; |
| | |
| | const label = document.createElement('label'); |
| | label.className = 'block text-gray-700 mb-2'; |
| | label.textContent = attribute.name; |
| | div.appendChild(label); |
| | |
| | let input; |
| | |
| | if (attribute.type === 'select') { |
| | input = document.createElement('select'); |
| | input.className = 'w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300'; |
| | |
| | const defaultOption = document.createElement('option'); |
| | defaultOption.value = ''; |
| | defaultOption.textContent = 'Sélectionner une option'; |
| | input.appendChild(defaultOption); |
| | |
| | const values = attribute.values.split(','); |
| | values.forEach(val => { |
| | const option = document.createElement('option'); |
| | option.value = val.trim(); |
| | option.textContent = val.trim(); |
| | input.appendChild(option); |
| | }); |
| | } else if (attribute.type === 'checkbox') { |
| | const container = document.createElement('div'); |
| | container.className = 'flex items-center'; |
| | |
| | input = document.createElement('input'); |
| | input.type = 'checkbox'; |
| | input.className = 'h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded'; |
| | |
| | const labelSpan = document.createElement('span'); |
| | labelSpan.className = 'ml-2 text-gray-700'; |
| | labelSpan.textContent = attribute.name; |
| | |
| | container.appendChild(input); |
| | container.appendChild(labelSpan); |
| | div.appendChild(container); |
| | } else { |
| | input = document.createElement('input'); |
| | input.type = attribute.type; |
| | input.className = 'w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-300'; |
| | } |
| | |
| | if (attribute.type !== 'checkbox') { |
| | div.appendChild(input); |
| | } |
| | |
| | productAttributesContainer.appendChild(div); |
| | }); |
| | } |
| | |
| | |
| | function saveProduct() { |
| | const id = parseInt(document.getElementById('product-id').value); |
| | const name = document.getElementById('product-name').value.trim(); |
| | const category = document.getElementById('product-category').value; |
| | const quantity = parseInt(document.getElementById('product-quantity').value); |
| | const price = parseFloat(document.getElementById('product-price').value); |
| | const description = document.getElementById('product-description').value.trim(); |
| | |
| | if (!name || !category || isNaN(quantity) || isNaN(price)) { |
| | showNotification('Veuillez remplir tous les champs obligatoires', 'error'); |
| | return; |
| | } |
| | |
| | const productData = { |
| | id, |
| | name, |
| | category, |
| | quantity, |
| | price, |
| | description: description || undefined |
| | }; |
| | |
| | |
| | const existingProductIndex = products.findIndex(p => p.id === id); |
| | |
| | if (existingProductIndex !== -1) { |
| | |
| | products[existingProductIndex] = productData; |
| | showNotification('Produit modifié avec succès', 'success'); |
| | } else { |
| | |
| | products.push(productData); |
| | showNotification('Produit ajouté avec succès', 'success'); |
| | } |
| | |
| | |
| | productModal.classList.add('hidden'); |
| | updateStatistics(); |
| | renderProductsTable(filteredProducts); |
| | } |
| | |
| | |
| | function deleteProduct(id) { |
| | if (confirm('Êtes-vous sûr de vouloir supprimer ce produit ?')) { |
| | products = products.filter(p => p.id !== id); |
| | showNotification('Produit supprimé avec succès', 'success'); |
| | updateStatistics(); |
| | |
| | |
| | const startIndex = (currentPage - 1) * itemsPerPage; |
| | if (startIndex >= filteredProducts.length && currentPage > 1) { |
| | currentPage--; |
| | } |
| | |
| | renderProductsTable(filteredProducts); |
| | } |
| | } |
| | |
| | |
| | function addCategory() { |
| | modalCategoryTitle.textContent = 'Ajouter une catégorie'; |
| | categoryForm.reset(); |
| | categoryModal.classList.remove('hidden'); |
| | |
| | |
| | const newId = categories.length > 0 ? Math.max(...categories.map(c => c.id)) + 1 : 1; |
| | document.getElementById('category-id').value = newId; |
| | |
| | |
| | updateCategoryAttributes(); |
| | } |
| | |
| | |
| | function editCategory(id) { |
| | const category = categories.find(c => c.id === id); |
| | if (!category) return; |
| | |
| | modalCategoryTitle.textContent = 'Modifier la catégorie'; |
| | categoryForm.reset(); |
| | |
| | document.getElementById('category-id').value = category.id; |
| | document.getElementById('category-name').value = category.name; |
| | document.getElementById('category-description').value = category.description || ''; |
| | document.getElementById('category-status').value = category.status; |
| | |
| | categoryModal.classList.remove('hidden'); |
| | |
| | |
| | updateCategoryAttributes(category.attributes); |
| | } |
| | |
| | |
| | function updateCategoryAttributes(selectedAttributes = []) { |
| | categoryAttributesContainer.innerHTML = ''; |
| | |
| | if (attributes.length === 0) { |
| | categoryAttributesContainer.innerHTML = '<p class="text-sm text-gray-500">Aucun attribut disponible</p>'; |
| | return; |
| | } |
| | |
| | attributes.forEach(attribute => { |
| | const div = document.createElement('div'); |
| | div.className = 'flex items-center'; |
| | |
| | const checkbox = document.createElement('input'); |
| | checkbox.type = 'checkbox'; |
| | checkbox.id = `attr-${attribute.id}`; |
| | checkbox.name = 'category-attributes'; |
| | checkbox.value = attribute.id; |
| | checkbox.className = 'h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded'; |
| | |
| | if (selectedAttributes.includes(attribute.id)) { |
| | checkbox.checked = true; |
| | } |
| | |
| | const label = document.createElement('label'); |
| | label.htmlFor = `attr-${attribute.id}`; |
| | label.className = 'ml-2 text-sm text-gray-700'; |
| | label.textContent = `${attribute.name} (${attribute.type})`; |
| | |
| | div.appendChild(checkbox); |
| | div.appendChild(label); |
| | categoryAttributesContainer.appendChild(div); |
| | }); |
| | } |
| | |
| | |
| | function saveCategory() { |
| | const id = parseInt(document.getElementById('category-id').value); |
| | const name = document.getElementById('category-name').value.trim(); |
| | const description = document.getElementById('category-description').value.trim(); |
| | const status = document.getElementById('category-status').value; |
| | |
| | if (!name) { |
| | showNotification('Veuillez remplir tous les champs obligatoires', 'error'); |
| | return; |
| | } |
| | |
| | |
| | const selectedAttributes = []; |
| | document.querySelectorAll('input[name="category-attributes"]:checked').forEach(checkbox => { |
| | selectedAttributes.push(parseInt(checkbox.value)); |
| | }); |
| | |
| | const categoryData = { |
| | id, |
| | name, |
| | description: description || undefined, |
| | status, |
| | attributes: selectedAttributes |
| | }; |
| | |
| | |
| | const existingCategoryIndex = categories.findIndex(c => c.id === id); |
| | |
| | if (existingCategoryIndex !== -1) { |
| | |
| | categories[existingCategoryIndex] = categoryData; |
| | showNotification('Catégorie modifiée avec succès', 'success'); |
| | } else { |
| | |
| | categories.push(categoryData); |
| | showNotification('Catégorie ajoutée avec succès', 'success'); |
| | } |
| | |
| | |
| | categoryModal.classList.add('hidden'); |
| | updateStatistics(); |
| | renderCategoriesTable(); |
| | populateCategoryFilter(); |
| | |
| | |
| | if (document.querySelector('.tab-button.active').dataset.tab === 'products') { |
| | renderProductsTable(); |
| | } |
| | } |
| | |
| | |
| | function deleteCategory(id) { |
| | |
| | const category = categories.find(c => c.id === id); |
| | if (!category) return; |
| | |
| | const productsUsingCategory = products.filter(p => p.category === category.name); |
| | |
| | if (productsUsingCategory.length > 0) { |
| | showNotification('Cette catégorie est utilisée par des produits et ne peut pas être supprimée', 'error'); |
| | return; |
| | } |
| | |
| | if (confirm('Êtes-vous sûr de vouloir supprimer cette catégorie ?')) { |
| | categories = categories.filter(c => c.id !== id); |
| | showNotification('Catégorie supprimée avec succès', 'success'); |
| | updateStatistics(); |
| | renderCategoriesTable(); |
| | populateCategoryFilter(); |
| | } |
| | } |
| | |
| | |
| | function addUser() { |
| | modalUserTitle.textContent = 'Ajouter un utilisateur'; |
| | userForm.reset(); |
| | userModal.classList.remove('hidden'); |
| | |
| | |
| | const newId = users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1; |
| | document.getElementById('user-id').value = newId; |
| | |
| | |
| | document.getElementById('user-password').required = true; |
| | } |
| | |
| | |
| | function editUser(id) { |
| | const user = users.find(u => u.id === id); |
| | if (!user) return; |
| | |
| | modalUserTitle.textContent = 'Modifier l\'utilisateur'; |
| | userForm.reset(); |
| | |
| | document.getElementById('user-id').value = user.id; |
| | document.getElementById('user-firstname').value = user.firstname; |
| | document.getElementById('user-lastname').value = user.lastname; |
| | document.getElementById('user-email').value = user.email; |
| | document.getElementById('user-role').value = user.role; |
| | document.getElementById('user-status').value = user.status; |
| | |
| | |
| | document.getElementById('user-password').required = false; |
| | |
| | userModal.classList.remove('hidden'); |
| | } |
| | |
| | |
| | function saveUser() { |
| | const id = parseInt(document.getElementById('user-id').value); |
| | const firstname = document.getElementById('user-firstname').value.trim(); |
| | const lastname = document.getElementById('user-lastname').value.trim(); |
| | const email = document.getElementById('user-email').value.trim(); |
| | const role = document.getElementById('user-role').value; |
| | const status = document.getElementById('user-status').value; |
| | const password = document.getElementById('user-password').value; |
| | |
| | if (!firstname || !lastname || !email || !role || !status) { |
| | showNotification('Veuillez remplir tous les champs obligatoires', 'error'); |
| | return; |
| | } |
| | |
| | |
| | const isNewUser = !users.some(u => u.id === id); |
| | if (isNewUser && !password) { |
| | showNotification('Veuillez saisir un mot de passe', 'error'); |
| | return; |
| | } |
| | |
| | const userData = { |
| | id, |
| | firstname, |
| | lastname, |
| | email, |
| | role, |
| | status |
| | }; |
| | |
| | |
| | if (password) { |
| | userData.password = password; |
| | } else if (isNewUser) { |
| | |
| | userData.password = Math.random().toString(36).slice(-8); |
| | } |
| | |
| | |
| | const existingUserIndex = users.findIndex(u => u.id === id); |
| | |
| | if (existingUserIndex !== -1) { |
| | |
| | if (!password) { |
| | userData.password = users[existingUserIndex].password; |
| | } |
| | |
| | users[existingUserIndex] = userData; |
| | showNotification('Utilisateur modifié avec succès', 'success'); |
| | } else { |
| | |
| | users.push(userData); |
| | showNotification('Utilisateur ajouté avec succès', 'success'); |
| | } |
| | |
| | |
| | userModal.classList.add('hidden'); |
| | updateStatistics(); |
| | renderUsersTable(); |
| | } |
| | |
| | |
| | function deleteUser(id) { |
| | |
| | if (id === 1) { |
| | |
| | </html> |