stocktech / index.html
syouness's picture
Add 1 files
f80f4a9 verified
<!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>
/* Animation pour les notifications */
@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;
}
/* Style personnalisé pour le tableau */
.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;
}
/* Style pour les onglets */
.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;
}
/* Style pour les badges de statut */
.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">
<!-- Barre de navigation -->
<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>
<!-- Contenu principal -->
<div class="container mx-auto px-4 py-6">
<!-- Onglets -->
<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>
<!-- Contenu des onglets -->
<div id="tab-contents">
<!-- Onglet Produits -->
<div id="products-tab" class="tab-content active">
<!-- En-tête et boutons d'action -->
<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>
<!-- Statistiques -->
<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>
<!-- Tableau des produits -->
<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>
<!-- Les options seront ajoutées dynamiquement -->
</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">
<!-- Les produits seront ajoutés ici dynamiquement -->
</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>
<!-- Onglet Catégories -->
<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>
<!-- Tableau des catégories -->
<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">
<!-- Les catégories seront ajoutées ici dynamiquement -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Onglet Utilisateurs -->
<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>
<!-- Tableau des utilisateurs -->
<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">
<!-- Les utilisateurs seront ajoutées ici dynamiquement -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Onglet Attributs -->
<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>
<!-- Tableau des attributs -->
<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">
<!-- Les attributs seront ajoutées ici dynamiquement -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Modal pour ajouter/modifier un produit -->
<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>
<!-- Les options seront ajoutées dynamiquement -->
</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">
<!-- Les attributs spécifiques seront ajoutés ici dynamiquement -->
</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>
<!-- Modal pour ajouter/modifier une catégorie -->
<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">
<!-- Les attributs seront ajoutés ici dynamiquement -->
</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>
<!-- Modal pour ajouter/modifier un utilisateur -->
<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>
<!-- Modal pour ajouter/modifier un attribut -->
<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">
<!-- Les catégories seront ajoutées ici dynamiquement -->
</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>
<!-- Zone de notifications -->
<div id="notifications" class="fixed bottom-4 right-4 space-y-2 z-50"></div>
<script>
// Données de l'application
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] }
];
// Éléments du DOM
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');
// Variables pour la pagination
let currentPage = 1;
const itemsPerPage = 5;
let filteredProducts = [];
// Variables pour les onglets
const tabButtons = document.querySelectorAll('.tab-button');
const tabContents = document.querySelectorAll('.tab-content');
// Initialisation de l'application
document.addEventListener('DOMContentLoaded', () => {
updateStatistics();
renderProductsTable();
renderCategoriesTable();
renderUsersTable();
renderAttributesTable();
populateCategoryFilter();
// Vérifier le mode sombre dans le localStorage
if (localStorage.getItem('darkMode') === 'enabled') {
document.body.classList.add('dark');
darkModeToggle.innerHTML = '<i class="fas fa-sun"></i>';
}
});
// Fonction pour afficher une notification
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);
// Fermer la notification après 5 secondes
setTimeout(() => {
notification.classList.add('hide');
setTimeout(() => notification.remove(), 500);
}, 5000);
// Fermer la notification au clic
notification.querySelector('.close-notification').addEventListener('click', () => {
notification.classList.add('hide');
setTimeout(() => notification.remove(), 500);
});
}
// Fonction pour mettre à jour les statistiques
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;
// Sauvegarder les données dans le localStorage
localStorage.setItem('products', JSON.stringify(products));
localStorage.setItem('categories', JSON.stringify(categories));
localStorage.setItem('users', JSON.stringify(users));
localStorage.setItem('attributes', JSON.stringify(attributes));
}
// Fonction pour remplir le filtre des catégories
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);
}
});
// Remplir aussi le select dans le modal produit
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);
}
});
}
// Fonction pour rendre le tableau des produits
function renderProductsTable(productsToRender = products) {
filteredProducts = productsToRender;
totalItemsElement.textContent = filteredProducts.length;
// Pagination
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const paginatedProducts = filteredProducts.slice(startIndex, endIndex);
displayedItemsElement.textContent = paginatedProducts.length;
// Activer/désactiver les boutons de pagination
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);
});
// Ajouter les événements aux boutons d'édition et de suppression
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);
});
});
}
// Fonction pour rendre le tableau des catégories
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);
});
// Ajouter les événements aux boutons d'édition et de suppression
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);
});
});
}
// Fonction pour rendre le tableau des utilisateurs
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);
});
// Ajouter les événements aux boutons d'édition et de suppression
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);
});
});
}
// Fonction pour rendre le tableau des attributs
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);
});
// Ajouter les événements aux boutons d'édition et de suppression
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);
});
});
}
// Fonction pour ajouter un nouveau produit
function addProduct() {
modalProductTitle.textContent = 'Ajouter un produit';
productForm.reset();
productModal.classList.remove('hidden');
// Générer un nouvel ID
const newId = products.length > 0 ? Math.max(...products.map(p => p.id)) + 1 : 1;
document.getElementById('product-id').value = newId;
// Ajouter les champs d'attributs dynamiques
updateProductAttributes();
}
// Fonction pour éditer un produit existant
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');
// Ajouter les champs d'attributs dynamiques
updateProductAttributes(product.category);
}
// Fonction pour mettre à jour les attributs du produit
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);
});
}
// Fonction pour sauvegarder un produit (ajout ou modification)
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
};
// Vérifier si c'est une modification ou un ajout
const existingProductIndex = products.findIndex(p => p.id === id);
if (existingProductIndex !== -1) {
// Modification
products[existingProductIndex] = productData;
showNotification('Produit modifié avec succès', 'success');
} else {
// Ajout
products.push(productData);
showNotification('Produit ajouté avec succès', 'success');
}
// Fermer le modal et mettre à jour l'interface
productModal.classList.add('hidden');
updateStatistics();
renderProductsTable(filteredProducts);
}
// Fonction pour supprimer un produit
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();
// Si la page actuelle est vide après suppression, revenir à la page précédente
const startIndex = (currentPage - 1) * itemsPerPage;
if (startIndex >= filteredProducts.length && currentPage > 1) {
currentPage--;
}
renderProductsTable(filteredProducts);
}
}
// Fonction pour ajouter une nouvelle catégorie
function addCategory() {
modalCategoryTitle.textContent = 'Ajouter une catégorie';
categoryForm.reset();
categoryModal.classList.remove('hidden');
// Générer un nouvel ID
const newId = categories.length > 0 ? Math.max(...categories.map(c => c.id)) + 1 : 1;
document.getElementById('category-id').value = newId;
// Mettre à jour les attributs disponibles
updateCategoryAttributes();
}
// Fonction pour éditer une catégorie existante
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');
// Mettre à jour les attributs disponibles
updateCategoryAttributes(category.attributes);
}
// Fonction pour mettre à jour les attributs de la catégorie
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);
});
}
// Fonction pour sauvegarder une catégorie (ajout ou modification)
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;
}
// Récupérer les attributs sélectionnés
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
};
// Vérifier si c'est une modification ou un ajout
const existingCategoryIndex = categories.findIndex(c => c.id === id);
if (existingCategoryIndex !== -1) {
// Modification
categories[existingCategoryIndex] = categoryData;
showNotification('Catégorie modifiée avec succès', 'success');
} else {
// Ajout
categories.push(categoryData);
showNotification('Catégorie ajoutée avec succès', 'success');
}
// Fermer le modal et mettre à jour l'interface
categoryModal.classList.add('hidden');
updateStatistics();
renderCategoriesTable();
populateCategoryFilter();
// Si on est sur l'onglet produits, recharger les produits pour mettre à jour les catégories
if (document.querySelector('.tab-button.active').dataset.tab === 'products') {
renderProductsTable();
}
}
// Fonction pour supprimer une catégorie
function deleteCategory(id) {
// Vérifier si la catégorie est utilisée par des produits
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();
}
}
// Fonction pour ajouter un nouvel utilisateur
function addUser() {
modalUserTitle.textContent = 'Ajouter un utilisateur';
userForm.reset();
userModal.classList.remove('hidden');
// Générer un nouvel ID
const newId = users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1;
document.getElementById('user-id').value = newId;
// Afficher le champ mot de passe et le rendre obligatoire pour un nouvel utilisateur
document.getElementById('user-password').required = true;
}
// Fonction pour éditer un utilisateur existant
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;
// Cacher le champ mot de passe et le rendre non obligatoire pour la modification
document.getElementById('user-password').required = false;
userModal.classList.remove('hidden');
}
// Fonction pour sauvegarder un utilisateur (ajout ou modification)
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;
}
// Pour un nouvel utilisateur, vérifier le mot de passe
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
};
// Si un mot de passe est fourni, l'ajouter/modifier
if (password) {
userData.password = password;
} else if (isNewUser) {
// Pour un nouvel utilisateur, générer un mot de passe aléatoire
userData.password = Math.random().toString(36).slice(-8);
}
// Vérifier si c'est une modification ou un ajout
const existingUserIndex = users.findIndex(u => u.id === id);
if (existingUserIndex !== -1) {
// Modification - conserver le mot de passe existant si aucun nouveau n'est fourni
if (!password) {
userData.password = users[existingUserIndex].password;
}
users[existingUserIndex] = userData;
showNotification('Utilisateur modifié avec succès', 'success');
} else {
// Ajout
users.push(userData);
showNotification('Utilisateur ajouté avec succès', 'success');
}
// Fermer le modal et mettre à jour l'interface
userModal.classList.add('hidden');
updateStatistics();
renderUsersTable();
}
// Fonction pour supprimer un utilisateur
function deleteUser(id) {
// Empêcher la suppression de l'utilisateur admin principal
if (id === 1) {
</html>