appnew / index.html
Dannylova31's picture
Update index.html
82939c2 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enregistrement Clients Pro</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@media (max-width: 640px) {
.mobile-view {
max-width: 100%;
margin: 0 auto;
border-radius: 0;
box-shadow: none;
height: 100vh;
}
}
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.rotate {
animation: rotate 2s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Custom scrollbar */
#data-list::-webkit-scrollbar {
width: 6px;
}
#data-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
#data-list::-webkit-scrollbar-thumb {
background: #c7d2fe;
border-radius: 10px;
}
#data-list::-webkit-scrollbar-thumb:hover {
background: #a5b4fc;
}
/* Tab styling */
.tab-button {
transition: all 0.3s ease;
}
.tab-button.active {
background-color: #4f46e5;
color: white;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
animation: fadeIn 0.3s ease-in-out;
}
/* Professional logo */
.logo-pro {
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-weight: 800;
letter-spacing: -0.05em;
}
.logo-icon {
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
color: white;
border-radius: 50%;
padding: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
}
/* Edit mode */
.edit-mode {
border: 2px solid #4f46e5;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.2);
}
/* Jean color for new form tab */
.jean-tab {
background-color: #5d8aa8;
color: white;
}
.jean-tab:hover {
background-color: #4a6f8a;
}
.jean-tab.active {
background-color: #3a5770;
}
</style>
</head>
<body class="bg-gray-100 font-sans">
<div class="container mx-auto px-4 py-8 max-w-md mobile-view bg-white">
<!-- Header with professional logo -->
<header class="mb-6">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center">
<div class="logo-icon mr-3">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
</div>
<div>
<h1 class="text-2xl font-bold logo-pro">
Fiche Client <span class="text-xs bg-indigo-100 text-indigo-800 px-2 py-1 rounded-full ml-1">PRO</span>
</h1>
<p class="text-xs text-gray-500 mt-1" id="current-date">Date: <span id="today-date"></span></p>
</div>
</div>
<div id="save-status" class="text-sm text-gray-500 flex items-center">
<span id="save-text">Sauvegardé</span>
<i id="save-icon" class="fas fa-check-circle ml-1 text-green-500"></i>
</div>
</div>
<div class="bg-indigo-50 p-3 rounded-lg border border-indigo-100">
<p class="text-indigo-800 text-sm flex items-start">
<i class="fas fa-info-circle mr-2 mt-1"></i>
<span>Les fiches clients sont sauvegardées automatiquement. Exportez en Excel ou PDF quand vous voulez.</span>
</p>
</div>
</header>
<!-- Tabs Navigation -->
<div class="flex border-b border-gray-200 mb-6">
<button class="tab-button jean-tab flex-1 py-2 px-4 text-center font-medium rounded-t-lg active" data-tab="form-tab">
<i class="fas fa-plus mr-2"></i> Nouvelle Fiche
</button>
<button class="tab-button flex-1 py-2 px-4 text-center font-medium rounded-t-lg" data-tab="list-tab">
<i class="fas fa-list mr-2"></i> Fiches Enregistrées
<span id="entries-count" class="ml-1 bg-indigo-100 text-indigo-800 text-xs py-0.5 px-1.5 rounded-full">
0
</span>
</button>
</div>
<!-- Main Content -->
<main>
<!-- Form Tab -->
<div id="form-tab" class="tab-content active">
<div class="mb-6">
<form id="data-form" class="space-y-3">
<input type="hidden" id="edit-id" name="edit-id" value="">
<!-- Date et Heure -->
<div class="grid grid-cols-2 gap-3">
<div>
<label for="date" class="block text-sm font-medium text-gray-700 mb-1">
<i class="fas fa-calendar-day mr-2"></i> Date*
</label>
<input type="date" id="date" name="date" required
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div>
<label for="time" class="block text-sm font-medium text-gray-700 mb-1">
<i class="fas fa-clock mr-2"></i> Heure*
</label>
<input type="time" id="time" name="time" required
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
</div>
<!-- Opérateur -->
<div class="grid grid-cols-2 gap-3">
<div>
<label for="operator-name" class="block text-sm font-medium text-gray-700 mb-1">
<i class="fas fa-user mr-2"></i> Nom Opérateur*
</label>
<input type="text" id="operator-name" name="operator-name" required
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div>
<label for="operator-number" class="block text-sm font-medium text-gray-700 mb-1">
<i class="fas fa-id-card mr-2"></i> N° Opérateur*
</label>
<input type="text" id="operator-number" name="operator-number" required
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
</div>
<!-- Technicien -->
<div>
<label for="technician" class="block text-sm font-medium text-gray-700 mb-1">
<i class="fas fa-tools mr-2"></i> Technicien*
</label>
<input type="text" id="technician" name="technician" required
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<!-- Client -->
<div class="grid grid-cols-2 gap-3">
<div>
<label for="client-name" class="block text-sm font-medium text-gray-700 mb-1">
<i class="fas fa-user-tag mr-2"></i> Nom Client*
</label>
<input type="text" id="client-name" name="client-name" required
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div>
<label for="client-number" class="block text-sm font-medium text-gray-700 mb-1">
<i class="fas fa-hashtag mr-2"></i> N° Client*
</label>
<input type="text" id="client-number" name="client-number" required
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
</div>
<!-- Commentaire -->
<div>
<label for="comment" class="block text-sm font-medium text-gray-700 mb-1">
<i class="fas fa-comment-dots mr-2"></i> Commentaire
</label>
<textarea id="comment" name="comment" rows="2"
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"></textarea>
</div>
<div class="pt-2 flex space-x-3">
<button type="submit" id="submit-button" class="flex-1 bg-indigo-600 text-white py-2 px-4 rounded-lg hover:bg-indigo-700 transition duration-200 flex items-center justify-center">
<i class="fas fa-save mr-2"></i> Enregistrer
</button>
<button type="button" id="cancel-edit" class="flex-1 bg-gray-200 text-gray-700 py-2 px-4 rounded-lg hover:bg-gray-300 transition duration-200 flex items-center justify-center hidden">
<i class="fas fa-times mr-2"></i> Annuler
</button>
</div>
</form>
</div>
</div>
<!-- List Tab -->
<div id="list-tab" class="tab-content">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-semibold text-gray-700">
<i class="fas fa-list-ol mr-2 text-indigo-500"></i> Fiches enregistrées
</h2>
<div class="flex space-x-2">
<button id="export-excel" class="bg-green-600 text-white px-3 py-1 rounded text-sm hover:bg-green-700 transition duration-200 flex items-center">
<i class="fas fa-file-excel mr-1"></i> Excel
</button>
<button id="export-pdf" class="bg-red-600 text-white px-3 py-1 rounded text-sm hover:bg-red-700 transition duration-200 flex items-center">
<i class="fas fa-file-pdf mr-1"></i> PDF
</button>
<button id="clear-all" class="bg-gray-200 text-gray-700 px-3 py-1 rounded text-sm hover:bg-gray-300 transition duration-200 flex items-center">
<i class="fas fa-trash-alt mr-1"></i> Tout effacer
</button>
</div>
</div>
<div id="data-list" class="space-y-3 max-h-96 overflow-y-auto pr-2">
<!-- Les fiches seront ajoutées ici dynamiquement -->
</div>
<div id="no-data" class="text-center py-8 text-gray-500">
<i class="fas fa-database text-4xl mb-2 opacity-30"></i>
<p>Aucune fiche enregistrée</p>
</div>
</div>
</main>
<!-- Footer -->
<footer class="mt-8 text-center text-sm text-gray-500 border-t pt-4">
<p>Application professionnelle d'enregistrement client</p>
<p class="mt-1">2025 Made Love ❤️ By Imed Ramdani & R.Nawel.
Version NL par Steve Demachaer.
All rights reserved.</p>
</footer>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const { jsPDF } = window.jspdf;
const dataForm = document.getElementById('data-form');
const dataList = document.getElementById('data-list');
const noData = document.getElementById('no-data');
const exportExcel = document.getElementById('export-excel');
const exportPdf = document.getElementById('export-pdf');
const clearAll = document.getElementById('clear-all');
const saveStatus = document.getElementById('save-status');
const saveText = document.getElementById('save-text');
const saveIcon = document.getElementById('save-icon');
const entriesCount = document.getElementById('entries-count');
const tabButtons = document.querySelectorAll('.tab-button');
const tabContents = document.querySelectorAll('.tab-content');
const submitButton = document.getElementById('submit-button');
const cancelEditButton = document.getElementById('cancel-edit');
const editIdInput = document.getElementById('edit-id');
const todayDateElement = document.getElementById('today-date');
// Set today's date in the header
const today = new Date();
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
todayDateElement.textContent = today.toLocaleDateString('fr-FR', options);
let entries = JSON.parse(localStorage.getItem('clientEntries')) || [];
let saveTimeout;
let isEditing = false;
// Set default date and time to now
const now = new Date();
document.getElementById('date').valueAsDate = now;
document.getElementById('time').value = now.toTimeString().substring(0, 5);
// Initial render
renderDataList();
updateEntriesCount();
// Tab switching
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const tabId = button.getAttribute('data-tab');
// Update active tab button
tabButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
// Update active tab content
tabContents.forEach(content => content.classList.remove('active'));
document.getElementById(tabId).classList.add('active');
});
});
// Form submission
dataForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(dataForm);
const entryId = formData.get('edit-id');
const entry = {
id: entryId || Date.now(),
date: formData.get('date'),
time: formData.get('time'),
operatorName: formData.get('operator-name'),
operatorNumber: formData.get('operator-number'),
technician: formData.get('technician'),
clientName: formData.get('client-name'),
clientNumber: formData.get('client-number'),
comment: formData.get('comment')
};
// Validate required fields
if (!entry.date || !entry.time || !entry.operatorName || !entry.operatorNumber ||
!entry.technician || !entry.clientName || !entry.clientNumber) {
showAlert('Veuillez remplir tous les champs obligatoires', 'error');
return;
}
// Format date for display (JJ/MM/AAAA)
const formattedDate = new Date(entry.date).toLocaleDateString('fr-FR');
if (isEditing) {
// Update existing entry
const index = entries.findIndex(e => e.id == entryId);
if (index !== -1) {
entries[index] = {
...entry,
displayDate: formattedDate
};
}
showAlert('Fiche client mise à jour avec succès', 'success');
} else {
// Add new entry
entries.unshift({
...entry,
displayDate: formattedDate
});
showAlert('Fiche client enregistrée avec succès', 'success');
}
saveData();
resetForm();
renderDataList();
updateEntriesCount();
// Switch to list tab after submission
document.querySelector('[data-tab="list-tab"]').click();
});
// Cancel edit
cancelEditButton.addEventListener('click', function() {
resetForm();
});
// Export to Excel
exportExcel.addEventListener('click', function() {
if (entries.length === 0) {
showAlert('Aucune fiche à exporter', 'error');
return;
}
// Create worksheet
const ws = XLSX.utils.json_to_sheet(entries.map(entry => ({
'Date': entry.displayDate,
'Heure': entry.time,
'Opérateur': entry.operatorName,
'N° Opérateur': entry.operatorNumber,
'Technicien': entry.technician,
'Client': entry.clientName,
'N° Client': entry.clientNumber,
'Commentaire': entry.comment || ''
})));
// Create workbook
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Fiches Clients");
// Export to file
XLSX.writeFile(wb, "fiches_clients.xlsx");
showAlert('Export Excel réussi', 'success');
});
// Export to PDF
exportPdf.addEventListener('click', function() {
if (entries.length === 0) {
showAlert('Aucune fiche à exporter', 'error');
return;
}
// Create a temporary div to hold the PDF content
const pdfContent = document.createElement('div');
pdfContent.style.padding = '20px';
pdfContent.style.backgroundColor = 'white';
pdfContent.style.maxWidth = '600px';
pdfContent.style.margin = '0 auto';
// Add title
const title = document.createElement('h1');
title.textContent = 'Fiches Clients Exportées';
title.style.textAlign = 'center';
title.style.marginBottom = '20px';
title.style.fontSize = '20px';
title.style.color = '#4f46e5';
title.style.fontWeight = 'bold';
pdfContent.appendChild(title);
// Add date
const exportDate = document.createElement('p');
exportDate.textContent = 'Exporté le: ' + new Date().toLocaleDateString('fr-FR');
exportDate.style.textAlign = 'center';
exportDate.style.marginBottom = '20px';
exportDate.style.color = '#6b7280';
pdfContent.appendChild(exportDate);
// Create table
const table = document.createElement('table');
table.style.width = '100%';
table.style.borderCollapse = 'collapse';
table.style.fontSize = '12px';
// Add table header
const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
headerRow.style.backgroundColor = '#eef2ff';
['Date', 'Heure', 'Opérateur', 'N° Op', 'Technicien', 'Client', 'N° Client', 'Commentaire'].forEach(text => {
const th = document.createElement('th');
th.textContent = text;
th.style.padding = '8px';
th.style.border = '1px solid #e5e7eb';
th.style.textAlign = 'left';
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// Add table body
const tbody = document.createElement('tbody');
entries.forEach(entry => {
const row = document.createElement('tr');
row.style.borderBottom = '1px solid #e5e7eb';
[
entry.displayDate,
entry.time,
entry.operatorName,
entry.operatorNumber,
entry.technician,
entry.clientName,
entry.clientNumber,
entry.comment || ''
].forEach(text => {
const td = document.createElement('td');
td.textContent = text;
td.style.padding = '8px';
td.style.border = '1px solid #e5e7eb';
row.appendChild(td);
});
tbody.appendChild(row);
});
table.appendChild(tbody);
pdfContent.appendChild(table);
// Add to document temporarily
document.body.appendChild(pdfContent);
// Generate PDF
html2canvas(pdfContent).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'landscape',
unit: 'mm'
});
const imgProps = pdf.getImageProperties(imgData);
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
pdf.save('fiches_clients.pdf');
// Remove temporary element
document.body.removeChild(pdfContent);
showAlert('Export PDF réussi', 'success');
});
});
// Clear all data
clearAll.addEventListener('click', function() {
if (entries.length === 0) return;
if (confirm('Êtes-vous sûr de vouloir supprimer toutes les fiches clients ?')) {
entries = [];
saveData();
renderDataList();
updateEntriesCount();
showAlert('Toutes les fiches ont été supprimées', 'success');
}
});
// Delete single entry
function deleteEntry(id) {
if (confirm('Êtes-vous sûr de vouloir supprimer cette fiche client ?')) {
entries = entries.filter(entry => entry.id !== id);
saveData();
renderDataList();
updateEntriesCount();
showAlert('Fiche supprimée', 'success');
}
}
// Edit single entry
function editEntry(id) {
const entry = entries.find(e => e.id == id);
if (!entry) return;
// Fill form with entry data
document.getElementById('edit-id').value = entry.id;
document.getElementById('date').value = entry.date;
document.getElementById('time').value = entry.time;
document.getElementById('operator-name').value = entry.operatorName;
document.getElementById('operator-number').value = entry.operatorNumber;
document.getElementById('technician').value = entry.technician;
document.getElementById('client-name').value = entry.clientName;
document.getElementById('client-number').value = entry.clientNumber;
document.getElementById('comment').value = entry.comment || '';
// Update UI for edit mode
isEditing = true;
submitButton.innerHTML = '<i class="fas fa-save mr-2"></i> Mettre à jour';
cancelEditButton.classList.remove('hidden');
document.querySelector('[data-tab="form-tab"]').click();
// Scroll to top
window.scrollTo(0, 0);
}
// Reset form to default state
function resetForm() {
dataForm.reset();
document.getElementById('edit-id').value = '';
isEditing = false;
submitButton.innerHTML = '<i class="fas fa-save mr-2"></i> Enregistrer';
cancelEditButton.classList.add('hidden');
// Reset date and time to now
const now = new Date();
document.getElementById('date').valueAsDate = now;
document.getElementById('time').value = now.toTimeString().substring(0, 5);
}
// Save data to localStorage
function saveData() {
// Show saving status
saveText.textContent = 'Sauvegarde...';
saveIcon.className = 'fas fa-circle-notch ml-1 text-indigo-500 rotate';
// Clear any existing timeout
if (saveTimeout) clearTimeout(saveTimeout);
// Set timeout to simulate async save
saveTimeout = setTimeout(() => {
localStorage.setItem('clientEntries', JSON.stringify(entries));
// Update status
saveText.textContent = 'Sauvegardé';
saveIcon.className = 'fas fa-check-circle ml-1 text-green-500';
// Reset status after 3 seconds
setTimeout(() => {
saveText.textContent = 'Auto-sauvegarde';
saveIcon.className = 'fas fa-history ml-1 text-gray-500';
}, 3000);
}, 800);
}
// Render data list
function renderDataList() {
if (entries.length === 0) {
dataList.innerHTML = '';
noData.style.display = 'block';
return;
}
noData.style.display = 'none';
dataList.innerHTML = entries.map(entry => `
<div class="bg-white p-3 rounded-lg shadow-sm border border-gray-100 fade-in" id="entry-${entry.id}">
<div class="flex justify-between items-start mb-1">
<div>
<span class="text-xs font-medium text-indigo-600">${entry.displayDate} à ${entry.time}</span>
</div>
<div class="flex space-x-2">
<button onclick="editEntry(${entry.id})" class="text-blue-500 text-xs hover:text-blue-700">
<i class="fas fa-edit mr-1"></i>
</button>
<button onclick="deleteEntry(${entry.id})" class="text-red-500 text-xs hover:text-red-700">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
<div class="grid grid-cols-2 gap-2 text-xs mb-2">
<div>
<span class="font-medium">Opérateur:</span>
<p>${entry.operatorName} (${entry.operatorNumber})</p>
</div>
<div>
<span class="font-medium">Technicien:</span>
<p>${entry.technician}</p>
</div>
</div>
<div class="grid grid-cols-2 gap-2 text-xs mb-2">
<div>
<span class="font-medium">Client:</span>
<p>${entry.clientName}</p>
</div>
<div>
<span class="font-medium">N° Client:</span>
<p>${entry.clientNumber}</p>
</div>
</div>
${entry.comment ? `
<div class="text-xs mt-1">
<span class="font-medium">Commentaire:</span>
<p class="text-gray-600">${entry.comment}</p>
</div>
` : ''}
</div>
`).join('');
}
// Update entries count in tab button
function updateEntriesCount() {
entriesCount.textContent = entries.length;
}
// Show alert message
function showAlert(message, type) {
const alert = document.createElement('div');
alert.className = `fixed top-4 left-1/2 transform -translate-x-1/2 px-6 py-3 rounded-lg shadow-lg text-white font-medium text-sm flex items-center ${
type === 'success' ? 'bg-green-500' : 'bg-red-500'
}`;
alert.innerHTML = `
<i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'} mr-2"></i>
${message}
`;
document.body.appendChild(alert);
setTimeout(() => {
alert.classList.add('opacity-0', 'transition-opacity', 'duration-300');
setTimeout(() => alert.remove(), 300);
}, 3000);
}
// Auto-save indicator
setInterval(() => {
if (saveText.textContent === 'Auto-sauvegarde') {
saveIcon.className = 'fas fa-circle-notch ml-1 text-indigo-500 rotate';
setTimeout(() => {
if (saveText.textContent === 'Auto-sauvegarde') {
saveIcon.className = 'fas fa-history ml-1 text-gray-500';
}
}, 1000);
}
}, 30000);
// Make functions available globally
window.deleteEntry = deleteEntry;
window.editEntry = editEntry;
});
</script>
</body>
</html>