test-space / index.html
sam7sam's picture
Add 1 files
225a864 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Samfact - Gestion Freelance Data Analyse</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.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">
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#0A84FF',
dark: '#0984FF'
},
secondary: {
DEFAULT: '#5E5CE6',
dark: '#5E5CE6'
},
background: {
DEFAULT: '#FFFFFF',
dark: '#1C1C1E'
},
surface: {
DEFAULT: '#F2F2F7',
dark: '#2C2C2E'
},
card: {
DEFAULT: '#FFFFFF',
dark: '#2C2C2E'
},
border: {
DEFAULT: '#D1D1D6',
dark: '#38383A'
},
text: {
DEFAULT: '#1C1C1E',
dark: '#FFFFFF'
},
subtitle: {
DEFAULT: '#636366',
dark: '#8E8E93'
}
},
fontFamily: {
sans: ['-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'sans-serif']
}
}
}
}
</script>
<style>
body {
@apply bg-background dark:bg-background-dark text-text dark:text-text-dark font-sans;
-webkit-font-smoothing: antialiased;
}
.sidebar {
transition: all 0.3s;
@apply bg-surface dark:bg-surface-dark border-r border-border dark:border-border-dark;
}
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
}
.sidebar.open {
transform: translateX(0);
}
}
.invoice-preview {
width: 210mm;
min-height: 297mm;
margin: auto;
padding: 20mm;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
@apply bg-card dark:bg-card-dark;
}
/* iOS-like elements */
.ios-card {
@apply rounded-xl shadow-sm overflow-hidden border border-border dark:border-border-dark;
}
.ios-button {
@apply rounded-full px-4 py-2 text-center font-medium text-white bg-primary dark:bg-primary-dark;
}
.ios-segmented-control {
@apply inline-flex rounded-full p-1 bg-surface dark:bg-surface-dark;
}
.ios-segmented-control button {
@apply px-4 py-1 rounded-full text-sm font-medium;
}
.ios-segmented-control button.active {
@apply bg-primary dark:bg-primary-dark text-white;
}
.ios-table-cell {
@apply px-4 py-3 border-b border-border dark:border-border-dark;
}
.ios-badge {
@apply rounded-full px-2 py-0.5 text-xs font-medium;
}
/* Dark mode toggle */
.dark-mode-toggle {
@apply w-12 h-6 rounded-full p-1 bg-gray-300 dark:bg-gray-600 flex items-center transition duration-300;
}
.dark-mode-toggle span {
@apply w-4 h-4 rounded-full bg-white dark:bg-gray-800 shadow-md transform transition duration-300 translate-x-0 dark:translate-x-6;
}
</style>
</head>
<body class="dark:bg-background-dark">
<!-- Dark Mode Toggle -->
<div class="fixed top-4 right-4 z-50">
<button id="darkModeToggle" class="dark-mode-toggle">
<span></span>
</button>
</div>
<!-- Mobile Menu Button -->
<button id="mobileMenuButton" class="md:hidden fixed top-4 left-4 z-40 bg-primary dark:bg-primary-dark text-white p-2 rounded-full">
<i class="fas fa-bars"></i>
</button>
<!-- Sidebar -->
<div id="sidebar" class="sidebar fixed top-0 left-0 h-full w-64 p-4 z-30">
<div class="flex items-center mb-8">
<div class="bg-primary dark:bg-primary-dark text-white rounded-xl p-3 mr-3">
<i class="fas fa-chart-line text-xl"></i>
</div>
<h1 class="text-2xl font-bold">Samfact</h1>
</div>
<nav>
<ul class="space-y-1">
<li>
<a href="#" id="dashboardLink" class="flex items-center p-3 rounded-lg hover:bg-surface dark:hover:bg-surface-dark transition active:bg-surface dark:active:bg-surface-dark">
<i class="fas fa-home mr-3 text-subtitle dark:text-subtitle-dark"></i> Tableau de bord
</a>
</li>
<li>
<a href="#" id="projectsLink" class="flex items-center p-3 rounded-lg hover:bg-surface dark:hover:bg-surface-dark transition active:bg-surface dark:active:bg-surface-dark">
<i class="fas fa-project-diagram mr-3 text-subtitle dark:text-subtitle-dark"></i> Projets
</a>
</li>
<li>
<a href="#" id="clientsLink" class="flex items-center p-3 rounded-lg hover:bg-surface dark:hover:bg-surface-dark transition active:bg-surface dark:active:bg-surface-dark">
<i class="fas fa-users mr-3 text-subtitle dark:text-subtitle-dark"></i> Clients
</a>
</li>
<li>
<a href="#" id="invoicesLink" class="flex items-center p-3 rounded-lg hover:bg-surface dark:hover:bg-surface-dark transition active:bg-surface dark:active:bg-surface-dark">
<i class="fas fa-file-invoice-dollar mr-3 text-subtitle dark:text-subtitle-dark"></i> Factures & Devis
</a>
</li>
<li>
<a href="#" id="settingsLink" class="flex items-center p-3 rounded-lg hover:bg-surface dark:hover:bg-surface-dark transition active:bg-surface dark:active:bg-surface-dark">
<i class="fas fa-cog mr-3 text-subtitle dark:text-subtitle-dark"></i> Paramètres
</a>
</li>
</ul>
</nav>
<div class="absolute bottom-0 left-0 w-full p-4">
<div class="bg-surface dark:bg-surface-dark p-3 rounded-xl">
<p class="text-sm text-subtitle dark:text-subtitle-dark">Auto-entrepreneur</p>
<p class="font-semibold">Data Analyse</p>
</div>
</div>
</div>
<!-- Main Content -->
<div class="ml-0 md:ml-64 transition-all duration-300">
<!-- Header -->
<header class="bg-card dark:bg-card-dark border-b border-border dark:border-border-dark p-4 flex justify-between items-center">
<h2 id="pageTitle" class="text-xl font-semibold">Tableau de bord</h2>
<div class="flex items-center space-x-4">
<div class="relative">
<button class="bg-surface dark:bg-surface-dark p-2 rounded-full hover:bg-surface dark:hover:bg-surface-dark transition">
<i class="fas fa-bell text-subtitle dark:text-subtitle-dark"></i>
</button>
<span class="absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full"></span>
</div>
<div class="flex items-center">
<div class="w-8 h-8 rounded-full bg-primary dark:bg-primary-dark flex items-center justify-center text-white">
<i class="fas fa-user"></i>
</div>
<span class="ml-2 font-medium hidden md:inline">Sam</span>
</div>
</div>
</header>
<!-- Dashboard Content -->
<main class="p-4">
<!-- Dashboard Section -->
<section id="dashboardSection">
<!-- Stats Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div class="ios-card">
<div class="flex items-center p-4">
<div class="p-3 rounded-lg bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400 mr-4">
<i class="fas fa-project-diagram"></i>
</div>
<div>
<p class="text-subtitle dark:text-subtitle-dark">Projets en cours</p>
<h3 id="activeProjectsCount" class="text-2xl font-bold">0</h3>
</div>
</div>
</div>
<div class="ios-card">
<div class="flex items-center p-4">
<div class="p-3 rounded-lg bg-green-100 dark:bg-green-900 text-green-600 dark:text-green-400 mr-4">
<i class="fas fa-calendar-day"></i>
</div>
<div>
<p class="text-subtitle dark:text-subtitle-dark">Jours travaillés</p>
<h3 id="daysWorkedCount" class="text-2xl font-bold">0</h3>
</div>
</div>
</div>
<div class="ios-card">
<div class="flex items-center p-4">
<div class="p-3 rounded-lg bg-purple-100 dark:bg-purple-900 text-purple-600 dark:text-purple-400 mr-4">
<i class="fas fa-euro-sign"></i>
</div>
<div>
<p class="text-subtitle dark:text-subtitle-dark">Revenus HT</p>
<h3 id="totalRevenue" class="text-2xl font-bold">0€</h3>
</div>
</div>
</div>
</div>
<!-- Chart -->
<div class="ios-card p-4 mb-6">
<div class="flex justify-between items-center mb-4">
<h3 class="font-semibold">Revenus par mois</h3>
<select id="chartRange" class="border border-border dark:border-border-dark rounded-full px-3 py-1 text-sm bg-card dark:bg-card-dark">
<option value="3">3 mois</option>
<option value="6">6 mois</option>
<option value="12">12 mois</option>
</select>
</div>
<canvas id="revenueChart" height="300"></canvas>
</div>
<!-- Projects Table -->
<div class="ios-card overflow-hidden">
<div class="flex justify-between items-center p-4 border-b border-border dark:border-border-dark">
<h3 class="font-semibold">Mes projets</h3>
<button id="addProjectBtn" class="ios-button flex items-center">
<i class="fas fa-plus mr-2"></i> Nouveau projet
</button>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-border dark:divide-border-dark">
<thead class="bg-surface dark:bg-surface-dark">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Projet</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Client</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Jours</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Forfait</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Total HT</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="projectsTableBody" class="divide-y divide-border dark:divide-border-dark">
<!-- Projects will be added here dynamically -->
</tbody>
</table>
</div>
</div>
</section>
<!-- Projects Section -->
<section id="projectsSection" class="hidden">
<div class="ios-card overflow-hidden mb-6">
<div class="flex justify-between items-center p-4 border-b border-border dark:border-border-dark">
<h3 class="font-semibold">Tous les projets</h3>
<button id="addProjectBtn2" class="ios-button flex items-center">
<i class="fas fa-plus mr-2"></i> Nouveau projet
</button>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-border dark:divide-border-dark">
<thead class="bg-surface dark:bg-surface-dark">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Projet</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Client</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Date début</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Date fin</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Jours</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Total HT</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Statut</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="allProjectsTableBody" class="divide-y divide-border dark:divide-border-dark">
<!-- All projects will be added here dynamically -->
</tbody>
</table>
</div>
</div>
</section>
<!-- Clients Section -->
<section id="clientsSection" class="hidden">
<div class="ios-card overflow-hidden mb-6">
<div class="flex justify-between items-center p-4 border-b border-border dark:border-border-dark">
<h3 class="font-semibold">Mes clients</h3>
<button id="addClientBtn" class="ios-button flex items-center">
<i class="fas fa-plus mr-2"></i> Nouveau client
</button>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-border dark:divide-border-dark">
<thead class="bg-surface dark:bg-surface-dark">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Nom</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Adresse</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">SIRET</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="clientsTableBody" class="divide-y divide-border dark:divide-border-dark">
<!-- Clients will be added here dynamically -->
</tbody>
</table>
</div>
</div>
</section>
<!-- Invoices Section -->
<section id="invoicesSection" class="hidden">
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<button id="allInvoicesBtn" class="ios-card p-4 text-left hover:bg-surface dark:hover:bg-surface-dark transition active:bg-surface dark:active:bg-surface-dark">
<p class="text-sm text-subtitle dark:text-subtitle-dark">Toutes</p>
<h3 id="allInvoicesCount" class="text-2xl font-bold">0</h3>
</button>
<button id="draftInvoicesBtn" class="ios-card p-4 text-left hover:bg-surface dark:hover:bg-surface-dark transition active:bg-surface dark:active:bg-surface-dark">
<p class="text-sm text-subtitle dark:text-subtitle-dark">Brouillons</p>
<h3 id="draftInvoicesCount" class="text-2xl font-bold">0</h3>
</button>
<button id="sentInvoicesBtn" class="ios-card p-4 text-left hover:bg-surface dark:hover:bg-surface-dark transition active:bg-surface dark:active:bg-surface-dark">
<p class="text-sm text-subtitle dark:text-subtitle-dark">Envoyées</p>
<h3 id="sentInvoicesCount" class="text-2xl font-bold">0</h3>
</button>
<button id="paidInvoicesBtn" class="ios-card p-4 text-left hover:bg-surface dark:hover:bg-surface-dark transition active:bg-surface dark:active:bg-surface-dark">
<p class="text-sm text-subtitle dark:text-subtitle-dark">Payées</p>
<h3 id="paidInvoicesCount" class="text-2xl font-bold">0</h3>
</button>
</div>
<div class="ios-card overflow-hidden">
<div class="flex justify-between items-center p-4 border-b border-border dark:border-border-dark">
<h3 class="font-semibold">Factures & Devis</h3>
<div class="flex space-x-2">
<button id="addInvoiceBtn" class="ios-button flex items-center">
<i class="fas fa-file-invoice-dollar mr-2"></i> Nouvelle facture
</button>
<button id="addQuoteBtn" class="bg-secondary dark:bg-secondary-dark text-white px-4 py-2 rounded-full hover:bg-opacity-90 transition flex items-center">
<i class="fas fa-file-signature mr-2"></i> Nouveau devis
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-border dark:divide-border-dark">
<thead class="bg-surface dark:bg-surface-dark">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Numéro</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Client</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Date</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Échéance</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Total HT</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Statut</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Type</th>
<th class="px-6 py-3 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="invoicesTableBody" class="divide-y divide-border dark:divide-border-dark">
<!-- Invoices will be added here dynamically -->
</tbody>
</table>
</div>
</div>
</section>
<!-- Settings Section -->
<section id="settingsSection" class="hidden">
<div class="ios-card overflow-hidden p-6">
<h3 class="font-semibold text-lg mb-6">Paramètres</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Personal Info -->
<div class="ios-card p-4">
<h4 class="font-medium mb-4">Informations personnelles</h4>
<div class="space-y-4">
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Nom complet</label>
<input type="text" id="userName" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark">
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Email</label>
<input type="email" id="userEmail" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark">
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Adresse</label>
<textarea id="userAddress" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark"></textarea>
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">SIRET</label>
<input type="text" id="userSiret" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark">
</div>
<button id="savePersonalInfoBtn" class="ios-button w-full">
Enregistrer
</button>
</div>
</div>
<!-- Business Settings -->
<div class="ios-card p-4">
<h4 class="font-medium mb-4">Paramètres professionnels</h4>
<div class="space-y-4">
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Forfait journalier par défaut (€)</label>
<input type="number" id="defaultRate" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark">
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Conditions de paiement</label>
<input type="text" id="paymentTerms" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark" placeholder="Ex: 30 jours net">
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Logo</label>
<div class="flex items-center space-x-4">
<div class="w-16 h-16 bg-surface dark:bg-surface-dark rounded-lg flex items-center justify-center overflow-hidden">
<img id="logoPreview" src="" alt="Logo" class="hidden max-w-full max-h-full">
<i class="fas fa-camera text-subtitle dark:text-subtitle-dark"></i>
</div>
<div>
<input type="file" id="logoUpload" class="hidden" accept="image/*">
<button id="uploadLogoBtn" class="text-sm bg-surface dark:bg-surface-dark hover:bg-opacity-80 px-3 py-1 rounded-lg">
Choisir un logo
</button>
</div>
</div>
</div>
<button id="saveBusinessSettingsBtn" class="ios-button w-full">
Enregistrer
</button>
</div>
</div>
</div>
</div>
</section>
</main>
</div>
<!-- Modals -->
<!-- Add Project Modal -->
<div id="projectModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="ios-card w-full max-w-md">
<div class="flex justify-between items-center p-4 border-b border-border dark:border-border-dark">
<h3 class="font-semibold text-lg">Nouveau projet</h3>
<button id="closeProjectModal" class="text-subtitle dark:text-subtitle-dark hover:text-text dark:hover:text-text-dark">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4">
<form id="projectForm">
<div class="space-y-4">
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Nom du projet*</label>
<input type="text" id="projectName" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark" required>
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Client*</label>
<select id="projectClient" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark" required>
<option value="">Sélectionner un client</option>
<!-- Clients will be added here dynamically -->
</select>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Date de début*</label>
<input type="date" id="projectStartDate" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark" required>
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Date de fin</label>
<input type="date" id="projectEndDate" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark">
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Jours travaillés*</label>
<input type="number" id="projectDays" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark" required>
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Forfait journalier (€)*</label>
<input type="number" id="projectRate" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark" required>
</div>
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Description</label>
<textarea id="projectDescription" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark"></textarea>
</div>
</div>
<div class="flex justify-end space-x-2 mt-6">
<button type="button" id="cancelProjectBtn" class="px-4 py-2 border border-border dark:border-border-dark rounded-lg hover:bg-surface dark:hover:bg-surface-dark">
Annuler
</button>
<button type="submit" class="ios-button">
Enregistrer
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add Client Modal -->
<div id="clientModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="ios-card w-full max-w-md">
<div class="flex justify-between items-center p-4 border-b border-border dark:border-border-dark">
<h3 class="font-semibold text-lg">Nouveau client</h3>
<button id="closeClientModal" class="text-subtitle dark:text-subtitle-dark hover:text-text dark:hover:text-text-dark">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4">
<form id="clientForm">
<div class="space-y-4">
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Nom*</label>
<input type="text" id="clientName" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark" required>
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Email*</label>
<input type="email" id="clientEmail" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark" required>
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Adresse</label>
<textarea id="clientAddress" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark"></textarea>
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">SIRET</label>
<input type="text" id="clientSiret" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark">
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Téléphone</label>
<input type="tel" id="clientPhone" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark">
</div>
</div>
<div class="flex justify-end space-x-2 mt-6">
<button type="button" id="cancelClientBtn" class="px-4 py-2 border border-border dark:border-border-dark rounded-lg hover:bg-surface dark:hover:bg-surface-dark">
Annuler
</button>
<button type="submit" class="ios-button">
Enregistrer
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add Invoice/Quote Modal -->
<div id="invoiceModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="ios-card w-full max-w-4xl">
<div class="flex justify-between items-center p-4 border-b border-border dark:border-border-dark">
<h3 id="invoiceModalTitle" class="font-semibold text-lg">Nouvelle facture</h3>
<button id="closeInvoiceModal" class="text-subtitle dark:text-subtitle-dark hover:text-text dark:hover:text-text-dark">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4">
<form id="invoiceForm">
<input type="hidden" id="invoiceType">
<input type="hidden" id="invoiceId">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<div class="space-y-4">
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Client*</label>
<select id="invoiceClient" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark" required>
<option value="">Sélectionner un client</option>
<!-- Clients will be added here dynamically -->
</select>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Date*</label>
<input type="date" id="invoiceDate" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark" required>
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Échéance</label>
<input type="date" id="invoiceDueDate" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark">
</div>
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Numéro</label>
<input type="text" id="invoiceNumber" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark" readonly>
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Statut</label>
<select id="invoiceStatus" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark">
<option value="draft">Brouillon</option>
<option value="sent">Envoyé</option>
<option value="paid">Payé</option>
</select>
</div>
</div>
</div>
<div>
<div class="space-y-4">
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Projet associé</label>
<select id="invoiceProject" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark">
<option value="">Aucun projet</option>
<!-- Projects will be added here dynamically -->
</select>
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Conditions de paiement</label>
<input type="text" id="invoicePaymentTerms" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark" placeholder="Ex: 30 jours net">
</div>
<div>
<label class="block text-sm text-subtitle dark:text-subtitle-dark mb-1">Notes</label>
<textarea id="invoiceNotes" class="w-full border border-border dark:border-border-dark rounded-lg px-3 py-2 bg-card dark:bg-card-dark"></textarea>
</div>
</div>
</div>
</div>
<div class="mt-6">
<h4 class="font-medium mb-2">Services</h4>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-border dark:divide-border-dark">
<thead class="bg-surface dark:bg-surface-dark">
<tr>
<th class="px-4 py-2 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Description</th>
<th class="px-4 py-2 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Jours</th>
<th class="px-4 py-2 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Forfait</th>
<th class="px-4 py-2 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider">Total</th>
<th class="px-4 py-2 text-left text-xs font-medium text-subtitle dark:text-subtitle-dark uppercase tracking-wider"></th>
</tr>
</thead>
<tbody id="invoiceItemsTable" class="divide-y divide-border dark:divide-border-dark">
<tr>
<td class="px-4 py-2"><input type="text" class="w-full border border-border dark:border-border-dark rounded-lg px-2 py-1 bg-card dark:bg-card-dark invoice-item-desc" placeholder="Description"></td>
<td class="px-4 py-2"><input type="number" class="w-full border border-border dark:border-border-dark rounded-lg px-2 py-1 bg-card dark:bg-card-dark invoice-item-days" placeholder="Jours"></td>
<td class="px-4 py-2"><input type="number" class="w-full border border-border dark:border-border-dark rounded-lg px-2 py-1 bg-card dark:bg-card-dark invoice-item-rate" placeholder="Forfait"></td>
<td class="px-4 py-2"><input type="text" class="w-full border border-border dark:border-border-dark rounded-lg px-2 py-1 bg-card dark:bg-card-dark invoice-item-total" placeholder="0.00" readonly></td>
<td class="px-4 py-2"><button type="button" class="text-red-500 hover:text-red-700 remove-item-btn"><i class="fas fa-trash"></i></button></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5" class="px-4 py-2">
<button type="button" id="addInvoiceItemBtn" class="text-primary dark:text-primary-dark hover:text-opacity-80 text-sm">
<i class="fas fa-plus mr-1"></i> Ajouter un service
</button>
</td>
</tr>
<tr>
<td colspan="3" class="px-4 py-2 text-right font-medium">Total HT</td>
<td colspan="2" class="px-4 py-2"><input type="text" id="invoiceSubtotal" class="w-full border border-border dark:border-border-dark rounded-lg px-2 py-1 bg-card dark:bg-card-dark" value="0.00" readonly></td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="flex justify-end space-x-2 mt-6">
<button type="button" id="cancelInvoiceBtn" class="px-4 py-2 border border-border dark:border-border-dark rounded-lg hover:bg-surface dark:hover:bg-surface-dark">
Annuler
</button>
<button type="submit" class="ios-button">
Enregistrer
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Invoice Preview Modal -->
<div id="invoicePreviewModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden overflow-y-auto">
<div class="ios-card w-full max-w-4xl m-4">
<div class="flex justify-between items-center p-4 border-b border-border dark:border-border-dark">
<h3 id="invoicePreviewTitle" class="font-semibold text-lg">Aperçu</h3>
<div class="flex space-x-2">
<button id="printInvoiceBtn" class="bg-surface dark:bg-surface-dark hover:bg-opacity-80 px-3 py-1 rounded-lg flex items-center">
<i class="fas fa-print mr-2"></i> Imprimer
</button>
<button id="downloadPdfBtn" class="ios-button px-3 py-1 flex items-center">
<i class="fas fa-file-pdf mr-2"></i> PDF
</button>
<button id="closeInvoicePreview" class="text-subtitle dark:text-subtitle-dark hover:text-text dark:hover:text-text-dark">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="p-4 overflow-auto">
<div id="invoicePreviewContent" class="invoice-preview">
<!-- Invoice content will be generated here -->
</div>
</div>
</div>
</div>
<!-- CSV Import Modal -->
<div id="csvImportModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="ios-card w-full max-w-md">
<div class="flex justify-between items-center p-4 border-b border-border dark:border-border-dark">
<h3 class="font-semibold text-lg">Importer des données</h3>
<button id="closeCsvImportModal" class="text-subtitle dark:text-subtitle-dark hover:text-text dark:hover:text-text-dark">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4">
<div class="mb-4">
<p class="text-sm text-subtitle dark:text-subtitle-dark">Sélectionnez un fichier CSV contenant vos projets existants.</p>
<p class="text-xs text-subtitle dark:text-subtitle-dark mt-1">Colonnes attendues: Projet, Client, Jours travaillés, Forfait journalier</p>
</div>
<div class="border-2 border-dashed border-border dark:border-border-dark rounded-lg p-6 text-center">
<input type="file" id="csvFileInput" class="hidden" accept=".csv">
<label for="csvFileInput" class="cursor-pointer">
<i class="fas fa-file-csv text-4xl text-subtitle dark:text-subtitle-dark mb-2"></i>
<p class="text-sm text-subtitle dark:text-subtitle-dark">Glissez-déposez un fichier ou cliquez pour sélectionner</p>
<p id="csvFileName" class="text-xs text-subtitle dark:text-subtitle-dark mt-2 hidden"></p>
</label>
</div>
<div class="flex justify-end space-x-2 mt-6">
<button type="button" id="cancelCsvImportBtn" class="px-4 py-2 border border-border dark:border-border-dark rounded-lg hover:bg-surface dark:hover:bg-surface-dark">
Annuler
</button>
<button type="button" id="confirmCsvImportBtn" class="ios-button disabled:opacity-50" disabled>
Importer
</button>
</div>
</div>
</div>
</div>
<script>
// Initialize the application when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
// Dark mode toggle
const darkModeToggle = document.getElementById('darkModeToggle');
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
// Check localStorage for dark mode preference
if (localStorage.getItem('darkMode') === 'enabled' ||
(!localStorage.getItem('darkMode') && prefersDarkScheme.matches)) {
document.documentElement.classList.add('dark');
darkModeToggle.classList.add('bg-primary-dark');
}
darkModeToggle.addEventListener('click', function() {
document.documentElement.classList.toggle('dark');
if (document.documentElement.classList.contains('dark')) {
localStorage.setItem('darkMode', 'enabled');
darkModeToggle.classList.add('bg-primary-dark');
} else {
localStorage.setItem('darkMode', 'disabled');
darkModeToggle.classList.remove('bg-primary-dark');
}
});
// Mobile menu toggle
const mobileMenuButton = document.getElementById('mobileMenuButton');
const sidebar = document.getElementById('sidebar');
mobileMenuButton.addEventListener('click', function() {
sidebar.classList.toggle('open');
});
// Navigation links
const navLinks = {
dashboardLink: document.getElementById('dashboardLink'),
projectsLink: document.getElementById('projectsLink'),
clientsLink: document.getElementById('clientsLink'),
invoicesLink: document.getElementById('invoicesLink'),
settingsLink: document.getElementById('settingsLink')
};
const sections = {
dashboardSection: document.getElementById('dashboardSection'),
projectsSection: document.getElementById('projectsSection'),
clientsSection: document.getElementById('clientsSection'),
invoicesSection: document.getElementById('invoicesSection'),
settingsSection: document.getElementById('settingsSection')
};
const pageTitle = document.getElementById('pageTitle');
// Show section and hide others
function showSection(sectionId, title) {
Object.values(sections).forEach(section => {
section.classList.add('hidden');
});
document.getElementById(sectionId).classList.remove('hidden');
pageTitle.textContent = title;
// Close sidebar on mobile after navigation
if (window.innerWidth <= 768) {
sidebar.classList.remove('open');
}
}
// Add event listeners to navigation links
navLinks.dashboardLink.addEventListener('click', function(e) {
e.preventDefault();
showSection('dashboardSection', 'Tableau de bord');
updateDashboardStats();
});
navLinks.projectsLink.addEventListener('click', function(e) {
e.preventDefault();
showSection('projectsSection', 'Mes projets');
renderAllProjectsTable();
});
navLinks.clientsLink.addEventListener('click', function(e) {
e.preventDefault();
showSection('clientsSection', 'Mes clients');
renderClientsTable();
});
navLinks.invoicesLink.addEventListener('click', function(e) {
e.preventDefault();
showSection('invoicesSection', 'Factures & Devis');
renderInvoicesTable();
});
navLinks.settingsLink.addEventListener('click', function(e) {
e.preventDefault();
showSection('settingsSection', 'Paramètres');
loadSettings();
});
// Initialize data storage
let appData = {
projects: [],
clients: [],
invoices: [],
settings: {
userName: "Sam Freelance",
userEmail: "sam@data-analyse.fr",
userAddress: "123 Rue des Entrepreneurs, 75000 Paris",
userSiret: "12345678900012",
defaultRate: 400,
paymentTerms: "30 jours net",
logo: ""
},
nextInvoiceNumber: 1,
nextQuoteNumber: 1
};
// Try to load data from localStorage
const savedData = localStorage.getItem('samfactData');
if (savedData) {
appData = JSON.parse(savedData);
} else {
// Add some sample data if no saved data exists
appData.clients = [
{ id: 1, name: "Entreprise A", email: "contact@entreprise-a.fr", address: "1 Rue des Exemples, Paris", siret: "12345678900001", phone: "0123456789" },
{ id: 2, name: "Startup B", email: "hello@startup-b.com", address: "5 Avenue des Tests, Lyon", siret: "98765432100001", phone: "0987654321" }
];
appData.projects = [
{ id: 1, name: "Analyse données marketing", clientId: 1, startDate: "2023-05-01", endDate: "2023-05-15", days: 10, rate: 400, description: "Analyse des campagnes marketing", status: "completed" },
{ id: 2, name: "Dashboard analytics", clientId: 2, startDate: "2023-06-01", endDate: "", days: 5, rate: 450, description: "Création de dashboard pour suivi KPI", status: "active" }
];
appData.invoices = [
{ id: 1, number: "F2023-001", type: "invoice", clientId: 1, date: "2023-05-16", dueDate: "2023-06-16", status: "paid", projectId: 1, paymentTerms: "30 jours net", notes: "", items: [
{ description: "Analyse données marketing", days: 10, rate: 400, total: 4000 }
]},
{ id: 2, number: "D2023-001", type: "quote", clientId: 2, date: "2023-05-20", dueDate: "", status: "sent", projectId: 2, paymentTerms: "30 jours net", notes: "Valable 30 jours", items: [
{ description: "Dashboard analytics", days: 5, rate: 450, total: 2250 }
]}
];
appData.nextInvoiceNumber = 3;
appData.nextQuoteNumber = 2;
saveData();
}
// Save data to localStorage
function saveData() {
localStorage.setItem('samfactData', JSON.stringify(appData));
}
// Dashboard functions
function updateDashboardStats() {
const activeProjects = appData.projects.filter(p => p.status === 'active').length;
const daysWorked = appData.projects.reduce((sum, project) => sum + (project.days || 0), 0);
const totalRevenue = appData.projects.reduce((sum, project) => sum + (project.days * project.rate || 0), 0);
document.getElementById('activeProjectsCount').textContent = activeProjects;
document.getElementById('daysWorkedCount').textContent = daysWorked;
document.getElementById('totalRevenue').textContent = totalRevenue.toLocaleString('fr-FR') + '€';
renderProjectsTable();
updateRevenueChart();
}
function renderProjectsTable() {
const tableBody = document.getElementById('projectsTableBody');
tableBody.innerHTML = '';
// Show only active projects on dashboard
const activeProjects = appData.projects.filter(p => p.status === 'active');
activeProjects.forEach(project => {
const client = appData.clients.find(c => c.id === project.clientId) || {};
const total = project.days * project.rate;
const row = document.createElement('tr');
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<div class="font-medium">${project.name}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div>${client.name || '-'}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div>${project.days}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div>${project.rate.toLocaleString('fr-FR')}€</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="font-medium">${total.toLocaleString('fr-FR')}€</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<button class="text-primary dark:text-primary-dark hover:text-opacity-80 mr-2 edit-project-btn" data-id="${project.id}">
<i class="fas fa-edit"></i>
</button>
<button class="text-red-500 hover:text-red-700 delete-project-btn" data-id="${project.id}">
<i class="fas fa-trash"></i>
</button>
</td>
`;
tableBody.appendChild(row);
});
// Add event listeners to buttons
document.querySelectorAll('.edit-project-btn').forEach(btn => {
btn.addEventListener('click', function() {
openEditProjectModal(parseInt(this.getAttribute('data-id')));
});
});
document.querySelectorAll('.delete-project-btn').forEach(btn => {
btn.addEventListener('click', function() {
if (confirm('Voulez-vous vraiment supprimer ce projet ?')) {
deleteProject(parseInt(this.getAttribute('data-id')));
}
});
});
}
function updateRevenueChart() {
const range = parseInt(document.getElementById('chartRange').value);
const now = new Date();
const months = [];
for (let i = range - 1; i >= 0; i--) {
const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
months.push(date.toLocaleDateString('fr-FR', { month: 'short', year: 'numeric' }));
}
const revenueData = new Array(range).fill(0);
appData.projects.forEach(project => {
const projectDate = new Date(project.startDate);
const monthDiff = (now.getFullYear() - projectDate.getFullYear()) * 12 + now.getMonth() - projectDate.getMonth();
if (monthDiff >= 0 && monthDiff < range) {
revenueData[range - 1 - monthDiff] += project.days * project.rate;
}
});
const ctx = document.getElementById('revenueChart').getContext('2d');
if (window.revenueChart) {
window.revenueChart.destroy();
}
// Set chart colors based on dark mode
const isDarkMode = document.documentElement.classList.contains('dark');
const bgColor = isDarkMode ? 'rgba(59, 130, 246, 0.5)' : 'rgba(10, 132, 255, 0.5)';
const borderColor = isDarkMode ? 'rgba(59, 130, 246, 1)' : 'rgba(10, 132, 255, 1)';
const textColor = isDarkMode ? '#FFFFFF' : '#1C1C1E';
const gridColor = isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
window.revenueChart = new Chart(ctx, {
type: 'bar',
data: {
labels: months,
datasets: [{
label: 'Revenus HT (€)',
data: revenueData,
backgroundColor: bgColor,
borderColor: borderColor,
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return value.toLocaleString('fr-FR') + '€';
},
color: textColor
},
grid: {
color: gridColor
}
},
x: {
ticks: {
color: textColor
},
grid: {
color: gridColor
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function(context) {
return context.dataset.label + ': ' + context.raw.toLocaleString('fr-FR') + '€';
}
}
},
legend: {
labels: {
color: textColor
}
}
}
}
});
document.getElementById('chartRange').addEventListener('change', updateRevenueChart);
}
// Projects functions
function renderAllProjectsTable() {
const tableBody = document.getElementById('allProjectsTableBody');
tableBody.innerHTML = '';
appData.projects.forEach(project => {
const client = appData.clients.find(c => c.id === project.clientId) || {};
const total = project.days * project.rate;
const statusClass = project.status === 'active' ? 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200' : 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200';
const statusText = project.status === 'active' ? 'Actif' : 'Terminé';
const row = document.createElement('tr');
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<div class="font-medium">${project.name}</div>
<div class="text-sm text-subtitle dark:text-subtitle-dark">${project.description || ''}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div>${client.name || '-'}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div>${project.startDate ? new Date(project.startDate).toLocaleDateString('fr-FR') : '-'}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div>${project.endDate ? new Date(project.endDate).toLocaleDateString('fr-FR') : '-'}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div>${project.days}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="font-medium">${total.toLocaleString('fr-FR')}€</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 text-xs rounded-full ${statusClass}">${statusText}</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<button class="text-primary dark:text-primary-dark hover:text-opacity-80 mr-2 edit-project-btn" data-id="${project.id}">
<i class="fas fa-edit"></i>
</button>
<button class="text-red-500 hover:text-red-700 delete-project-btn" data-id="${project.id}">
<i class="fas fa-trash"></i>
</button>
</td>
`;
tableBody.appendChild(row);
});
// Add event listeners to buttons
document.querySelectorAll('.edit-project-btn').forEach(btn => {
btn.addEventListener('click', function() {
openEditProjectModal(parseInt(this.getAttribute('data-id')));
});
});
document.querySelectorAll('.delete-project-btn').forEach(btn => {
btn.addEventListener('click', function() {
if (confirm('Voulez-vous vraiment supprimer ce projet ?')) {
deleteProject(parseInt(this.getAttribute('data-id')));
}
});
});
}
// Project modal functions
const projectModal = document.getElementById('projectModal');
const projectForm = document.getElementById('projectForm');
const closeProjectModal = document.getElementById('closeProjectModal');
const cancelProjectBtn = document.getElementById('cancelProjectBtn');
// Open modal for new project
document.getElementById('addProjectBtn').addEventListener('click', openNewProjectModal);
document.getElementById('addProjectBtn2').addEventListener('click', openNewProjectModal);
function openNewProjectModal() {
document.getElementById('projectForm').reset();
document.getElementById('projectModal').querySelector('h3').textContent = 'Nouveau projet';
// Populate client dropdown
const clientSelect = document.getElementById('projectClient');
clientSelect.innerHTML = '<option value="">Sélectionner un client</option>';
appData.clients.forEach(client => {
const option = document.createElement('option');
option.value = client.id;
option.textContent = client.name;
clientSelect.appendChild(option);
});
// Set default rate from settings
document.getElementById('projectRate').value = appData.settings.defaultRate || '';
projectModal.classList.remove('hidden');
}
function openEditProjectModal(projectId) {
const project = appData.projects.find(p => p.id === projectId);
if (!project) return;
document.getElementById('projectModal').querySelector('h3').textContent = 'Modifier projet';
// Populate client dropdown
const clientSelect = document.getElementById('projectClient');
clientSelect.innerHTML = '<option value="">Sélectionner un client</option>';
appData.clients.forEach(client => {
const option = document.createElement('option');
option.value = client.id;
option.textContent = client.name;
option.selected = client.id === project.clientId;
clientSelect.appendChild(option);
});
// Fill form with project data
document.getElementById('projectName').value = project.name || '';
document.getElementById('projectStartDate').value = project.startDate || '';
document.getElementById('projectEndDate').value = project.endDate || '';
document.getElementById('projectDays').value = project.days || '';
document.getElementById('projectRate').value = project.rate || '';
document.getElementById('projectDescription').value = project.description || '';
// Store project ID in form
projectForm.setAttribute('data-project-id', projectId);
projectModal.classList.remove('hidden');
}
// Close modal
closeProjectModal.addEventListener('click', function() {
projectModal.classList.add('hidden');
});
cancelProjectBtn.addEventListener('click', function() {
projectModal.classList.add('hidden');
});
// Handle form submission
projectForm.addEventListener('submit', function(e) {
e.preventDefault();
const projectId = parseInt(this.getAttribute('data-project-id')) || null;
const isEdit = !!projectId;
const projectData = {
name: document.getElementById('projectName').value,
clientId: parseInt(document.getElementById('projectClient').value),
startDate: document.getElementById('projectStartDate').value,
endDate: document.getElementById('projectEndDate').value || null,
days: parseInt(document.getElementById('projectDays').value),
rate: parseFloat(document.getElementById('projectRate').value),
description: document.getElementById('projectDescription').value,
status: 'active'
};
if (isEdit) {
// Update existing project
const index = appData.projects.findIndex(p => p.id === projectId);
if (index !== -1) {
appData.projects[index] = { ...appData.projects[index], ...projectData };
}
} else {
// Add new project
const newId = appData.projects.length > 0 ? Math.max(...appData.projects.map(p => p.id)) + 1 : 1;
appData.projects.push({ id: newId, ...projectData });
}
saveData();
projectModal.classList.add('hidden');
// Update UI
if (isEdit) {
renderProjectsTable();
renderAllProjectsTable();
} else {
updateDashboardStats();
renderAllProjectsTable();
}
});
function deleteProject(projectId) {
appData.projects = appData.projects.filter(p => p.id !== projectId);
saveData();
// Update UI
updateDashboardStats();
renderAllProjectsTable();
}
// Clients functions
function renderClientsTable() {
const tableBody = document.getElementById('clientsTableBody');
tableBody.innerHTML = '';
appData.clients.forEach(client => {
const row = document.createElement('tr');
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<div class="font-medium">${client.name}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div>${client.email}</div>
</td>
<td class="px-6 py-4">
<div>${client.address || '-'}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div>${client.siret || '-'}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<button class="text-primary dark:text-primary-dark hover:text-opacity-80 mr-2 edit-client-btn" data-id="${client.id}">
<i class="fas fa-edit"></i>
</button>
<button class="text-red-500 hover:text-red-700 delete-client-btn" data-id="${client.id}">
<i class="fas fa-trash"></i>
</button>
</td>
`;
tableBody.appendChild(row);
});
// Add event listeners to buttons
document.querySelectorAll('.edit-client-btn').forEach(btn => {
btn.addEventListener('click', function() {
openEditClientModal(parseInt(this.getAttribute('data-id')));
});
});
document.querySelectorAll('.delete-client-btn').forEach(btn => {
btn.addEventListener('click', function() {
if (confirm('Voulez-vous vraiment supprimer ce client ?')) {
deleteClient(parseInt(this.getAttribute('data-id')));
}
});
});
}
// Client modal functions
const clientModal = document.getElementById('clientModal');
const clientForm = document.getElementById('clientForm');
const closeClientModal = document.getElementById('closeClientModal');
const cancelClientBtn = document.getElementById('cancelClientBtn');
// Open modal for new client
document.getElementById('addClientBtn').addEventListener('click', openNewClientModal);
function openNewClientModal() {
document.getElementById('clientForm').reset();
document.getElementById('clientModal').querySelector('h3').textContent = 'Nouveau client';
clientForm.removeAttribute('data-client-id');
clientModal.classList.remove('hidden');
}
function openEditClientModal(clientId) {
const client = appData.clients.find(c => c.id === clientId);
if (!client) return;
document.getElementById('clientModal').querySelector('h3').textContent = 'Modifier client';
// Fill form with client data
document.getElementById('clientName').value = client.name || '';
document.getElementById('clientEmail').value = client.email || '';
document.getElementById('clientAddress').value = client.address || '';
document.getElementById('clientSiret').value = client.siret || '';
document.getElementById('clientPhone').value = client.phone || '';
// Store client ID in form
clientForm.setAttribute('data-client-id', clientId);
clientModal.classList.remove('hidden');
}
// Close modal
closeClientModal.addEventListener('click', function() {
clientModal.classList.add('hidden');
});
cancelClientBtn.addEventListener('click', function() {
clientModal.classList.add('hidden');
});
// Handle form submission
clientForm.addEventListener('submit', function(e) {
e.preventDefault();
const clientId = parseInt(this.getAttribute('data-client-id')) || null;
const isEdit = !!clientId;
const clientData = {
name: document.getElementById('clientName').value,
email: document.getElementById('clientEmail').value,
address: document.getElementById('clientAddress').value,
siret: document.getElementById('clientSiret').value,
phone: document.getElementById('clientPhone').value
};
if (isEdit) {
// Update existing client
const index = appData.clients.findIndex(c => c.id === clientId);
if (index !== -1) {
appData.clients[index] = { ...appData.clients[index], ...clientData };
}
} else {
// Add new client
const newId = appData.clients.length > 0 ? Math.max(...appData.clients.map(c => c.id)) + 1 : 1;
appData.clients.push({ id: newId, ...clientData });
}
saveData();
clientModal.classList.add('hidden');
// Update UI
renderClientsTable();
// Also update client dropdowns in other forms
if (!isEdit) {
updateDashboardStats();
renderAllProjectsTable();
}
});
function deleteClient(clientId) {
// Check if client is used in any projects or invoices
const hasProjects = appData.projects.some(p => p.clientId === clientId);
const hasInvoices = appData.invoices.some(i => i.clientId === clientId);
if (hasProjects || hasInvoices) {
alert('Ce client est associé à des projets ou factures et ne peut pas être supprimé.');
return;
}
appData.clients = appData.clients.filter(c => c.id !== clientId);
saveData();
// Update UI
renderClientsTable();
}
// Invoices functions
function renderInvoicesTable(filter = null) {
const tableBody = document.getElementById('invoicesTableBody');
tableBody.innerHTML = '';
// Update counters
const allInvoices = appData.invoices.length;
const draftInvoices = appData.invoices.filter(i => i.status === 'draft').length;
const sentInvoices = appData.invoices.filter(i => i.status === 'sent').length;
const paidInvoices = appData.invoices.filter(i => i.status === 'paid').length;
document.getElementById('allInvoicesCount').textContent = allInvoices;
document.getElementById('draftInvoicesCount').textContent = draftInvoices;
document.getElementById('sentInvoicesCount').textContent = sentInvoices;
document.getElementById('paidInvoicesCount').textContent = paidInvoices;
// Filter invoices if needed
let invoicesToShow = appData.invoices;
if (filter === 'draft') {
invoicesToShow = appData.invoices.filter(i => i.status === 'draft');
} else if (filter === 'sent') {
invoicesToShow = appData.invoices.filter(i => i.status === 'sent');
} else if (filter === 'paid') {
invoicesToShow
</html>