feat: gestion des factures, génération PDF, corrections DB, .gitignore
This commit is contained in:
106
static/dashboard.html
Normal file
106
static/dashboard.html
Normal file
@@ -0,0 +1,106 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gestion des Factures</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/styles.css">
|
||||
</head>
|
||||
<body class="bg-gray-100 min-h-screen">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<h1 class="text-3xl font-bold">Gestion des Factures</h1>
|
||||
<a href="generator" class="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600">
|
||||
Nouvelle Facture
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Statistiques -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
||||
<div class="bg-white rounded-lg shadow p-4">
|
||||
<h3 class="text-gray-500 text-sm">Total Factures</h3>
|
||||
<p class="text-2xl font-bold" id="totalInvoices">0</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow p-4">
|
||||
<h3 class="text-gray-500 text-sm">Montant Total</h3>
|
||||
<p class="text-2xl font-bold" id="totalAmount">0 €</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow p-4">
|
||||
<h3 class="text-gray-500 text-sm">Factures Payées</h3>
|
||||
<p class="text-2xl font-bold text-green-500" id="totalPaid">0 €</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow p-4">
|
||||
<h3 class="text-gray-500 text-sm">Factures en Retard</h3>
|
||||
<p class="text-2xl font-bold text-red-500" id="totalOverdue">0 €</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filtres -->
|
||||
<div class="bg-white rounded-lg shadow p-4 mb-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Statut</label>
|
||||
<select id="statusFilter" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
<option value="">Tous</option>
|
||||
<option value="issued">Émise</option>
|
||||
<option value="paid">Payée</option>
|
||||
<option value="overdue">En retard</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Date de début</label>
|
||||
<input type="date" id="dateFrom" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Date de fin</label>
|
||||
<input type="date" id="dateTo" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button id="applyFilters" class="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 w-full">
|
||||
Appliquer les filtres
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Liste des factures -->
|
||||
<div class="bg-white rounded-lg shadow overflow-hidden">
|
||||
<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">N° Facture</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Client</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Montant</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 class="bg-white divide-y divide-gray-200" id="invoicesList">
|
||||
<!-- Les factures seront ajoutées ici dynamiquement -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal pour les détails de la facture -->
|
||||
<div id="invoiceModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden">
|
||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||
<div class="mt-3 text-center">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modalTitle"></h3>
|
||||
<div class="mt-2 px-7 py-3">
|
||||
<p class="text-sm text-gray-500" id="modalContent"></p>
|
||||
</div>
|
||||
<div class="items-center px-4 py-3">
|
||||
<button id="closeModal" class="px-4 py-2 bg-gray-500 text-white text-base font-medium rounded-md w-full shadow-sm hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-300">
|
||||
Fermer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/dashboard.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
204
static/dashboard.js
Normal file
204
static/dashboard.js
Normal file
@@ -0,0 +1,204 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Éléments du DOM
|
||||
const invoicesList = document.getElementById("invoicesList");
|
||||
const statusFilter = document.getElementById("statusFilter");
|
||||
const dateFrom = document.getElementById("dateFrom");
|
||||
const dateTo = document.getElementById("dateTo");
|
||||
const applyFilters = document.getElementById("applyFilters");
|
||||
const modal = document.getElementById("invoiceModal");
|
||||
const modalTitle = document.getElementById("modalTitle");
|
||||
const modalContent = document.getElementById("modalContent");
|
||||
const closeModal = document.getElementById("closeModal");
|
||||
|
||||
// Charger les factures au démarrage
|
||||
loadInvoices();
|
||||
|
||||
// Gestionnaire d'événements pour les filtres
|
||||
applyFilters.addEventListener("click", () => {
|
||||
loadInvoices({
|
||||
status: statusFilter.value,
|
||||
date_from: dateFrom.value,
|
||||
date_to: dateTo.value,
|
||||
});
|
||||
});
|
||||
|
||||
// Gestionnaire d'événements pour fermer le modal
|
||||
closeModal.addEventListener("click", () => {
|
||||
modal.classList.add("hidden");
|
||||
});
|
||||
|
||||
// Fonction pour charger les factures
|
||||
async function loadInvoices(filters = {}) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
"/api/invoices?" + new URLSearchParams(filters)
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
// Mettre à jour les statistiques
|
||||
updateStatistics(data.statistics);
|
||||
|
||||
// Mettre à jour la liste des factures
|
||||
displayInvoices(data.invoices);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du chargement des factures:", error);
|
||||
showMessage("Erreur lors du chargement des factures", "error");
|
||||
}
|
||||
}
|
||||
|
||||
// Fonction pour mettre à jour les statistiques
|
||||
function updateStatistics(stats) {
|
||||
document.getElementById("totalInvoices").textContent = stats.total_invoices;
|
||||
document.getElementById(
|
||||
"totalAmount"
|
||||
).textContent = `${stats.total_amount.toFixed(2)} €`;
|
||||
document.getElementById(
|
||||
"totalPaid"
|
||||
).textContent = `${stats.total_paid.toFixed(2)} €`;
|
||||
document.getElementById(
|
||||
"totalOverdue"
|
||||
).textContent = `${stats.total_overdue.toFixed(2)} €`;
|
||||
}
|
||||
|
||||
// Fonction pour afficher les factures
|
||||
function displayInvoices(invoices) {
|
||||
invoicesList.innerHTML = "";
|
||||
|
||||
invoices.forEach((invoice) => {
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
${invoice.invoice_number}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
${invoice.client_name}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
${new Date(invoice.issue_date).toLocaleDateString()}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
${invoice.amount} ${invoice.currency}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
|
||||
${getStatusClass(invoice.status)}">
|
||||
${getStatusText(invoice.status)}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<button onclick="viewInvoice(${
|
||||
invoice.id
|
||||
})" class="text-blue-600 hover:text-blue-900 mr-3">
|
||||
Voir
|
||||
</button>
|
||||
<button onclick="updateStatus(${
|
||||
invoice.id
|
||||
})" class="text-green-600 hover:text-green-900 mr-3">
|
||||
Marquer comme payée
|
||||
</button>
|
||||
<button onclick="downloadInvoice(${
|
||||
invoice.id
|
||||
})" class="text-gray-600 hover:text-gray-900">
|
||||
Télécharger
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
invoicesList.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Fonction pour obtenir la classe CSS du statut
|
||||
function getStatusClass(status) {
|
||||
switch (status) {
|
||||
case "paid":
|
||||
return "bg-green-100 text-green-800";
|
||||
case "overdue":
|
||||
return "bg-red-100 text-red-800";
|
||||
default:
|
||||
return "bg-yellow-100 text-yellow-800";
|
||||
}
|
||||
}
|
||||
|
||||
// Fonction pour obtenir le texte du statut
|
||||
function getStatusText(status) {
|
||||
switch (status) {
|
||||
case "paid":
|
||||
return "Payée";
|
||||
case "overdue":
|
||||
return "En retard";
|
||||
default:
|
||||
return "Émise";
|
||||
}
|
||||
}
|
||||
|
||||
// Fonction pour afficher une facture
|
||||
window.viewInvoice = async (invoiceId) => {
|
||||
try {
|
||||
const response = await fetch(`/api/invoices/${invoiceId}`);
|
||||
const invoice = await response.json();
|
||||
|
||||
modalTitle.textContent = `Facture ${invoice.invoice_number}`;
|
||||
modalContent.innerHTML = `
|
||||
<div class="text-left">
|
||||
<p><strong>Client:</strong> ${invoice.client_name}</p>
|
||||
<p><strong>Date:</strong> ${new Date(
|
||||
invoice.issue_date
|
||||
).toLocaleDateString()}</p>
|
||||
<p><strong>Montant:</strong> ${invoice.amount} ${
|
||||
invoice.currency
|
||||
}</p>
|
||||
<p><strong>Statut:</strong> ${getStatusText(
|
||||
invoice.status
|
||||
)}</p>
|
||||
<p><strong>Adresse:</strong> ${invoice.address}</p>
|
||||
<p>${invoice.postal_code} ${invoice.town}</p>
|
||||
<p>${invoice.country}</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.classList.remove("hidden");
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du chargement de la facture:", error);
|
||||
showMessage("Erreur lors du chargement de la facture", "error");
|
||||
}
|
||||
};
|
||||
|
||||
// Fonction pour mettre à jour le statut d'une facture
|
||||
window.updateStatus = async (invoiceId) => {
|
||||
try {
|
||||
await fetch(`/api/invoices/${invoiceId}/status`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ status: "paid" }),
|
||||
});
|
||||
|
||||
loadInvoices();
|
||||
showMessage("Statut de la facture mis à jour");
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la mise à jour du statut:", error);
|
||||
showMessage("Erreur lors de la mise à jour du statut", "error");
|
||||
}
|
||||
};
|
||||
|
||||
// Fonction pour télécharger une facture
|
||||
window.downloadInvoice = async (invoiceId) => {
|
||||
try {
|
||||
const response = await fetch(`/api/invoices/${invoiceId}/download`);
|
||||
const blob = await response.blob();
|
||||
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `invoice_${invoiceId}.pdf`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du téléchargement de la facture:", error);
|
||||
showMessage("Erreur lors du téléchargement de la facture", "error");
|
||||
}
|
||||
};
|
||||
});
|
||||
99
static/generator.html
Normal file
99
static/generator.html
Normal file
@@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Générateur de Factures</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/styles.css">
|
||||
</head>
|
||||
<body class="bg-gray-100 min-h-screen">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<h1 class="text-3xl font-bold">Générateur de Factures</h1>
|
||||
<a href="/dashboard" class="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600">
|
||||
Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form id="invoiceForm" class="max-w-2xl mx-auto bg-white rounded-lg shadow-md p-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Informations générales -->
|
||||
<div class="space-y-4">
|
||||
<h2 class="text-xl font-semibold mb-4">Informations générales</h2>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Langue</label>
|
||||
<select name="language" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
<option value="français">Français</option>
|
||||
<option value="deutsch">Deutsch</option>
|
||||
<option value="english">English</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Numéro de facture</label>
|
||||
<input type="text" name="invoice_number" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" placeholder="001">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Montant</label>
|
||||
<input type="number" step="0.01" name="amount" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" placeholder="0.00">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Devise</label>
|
||||
<select name="currency" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
<option value="EUR">EUR</option>
|
||||
<option value="CHF">CHF</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Informations du destinataire -->
|
||||
<div class="space-y-4">
|
||||
<h2 class="text-xl font-semibold mb-4">Destinataire</h2>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Nom</label>
|
||||
<input type="text" name="recipient_name" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Adresse</label>
|
||||
<input type="text" name="recipient_address" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Code postal</label>
|
||||
<input type="text" name="recipient_postal_code" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Ville</label>
|
||||
<input type="text" name="recipient_town" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Pays</label>
|
||||
<input type="text" name="recipient_country" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Numéro de TVA (optionnel)</label>
|
||||
<input type="text" name="recipient_vat_number" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex justify-center">
|
||||
<button type="submit" class="bg-blue-500 text-white px-6 py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
||||
Générer la facture
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="/static/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
68
static/preview.html
Normal file
68
static/preview.html
Normal file
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Prévisualisation de la Facture</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/styles.css">
|
||||
</head>
|
||||
<body class="bg-gray-100 min-h-screen">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<h1 class="text-3xl font-bold">Prévisualisation de la Facture</h1>
|
||||
<div class="space-x-4">
|
||||
<a href="/generator" class="bg-gray-500 text-white px-4 py-2 rounded-md hover:bg-gray-600">
|
||||
Retour
|
||||
</a>
|
||||
<button id="validateInvoice" class="bg-green-500 text-white px-4 py-2 rounded-md hover:bg-green-600">
|
||||
Valider la Facture
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md p-8 max-w-4xl mx-auto">
|
||||
<div class="grid grid-cols-2 gap-8">
|
||||
<!-- Informations de l'émetteur -->
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold mb-4">Émetteur</h2>
|
||||
<div class="space-y-2">
|
||||
<p>Cheap Motion Pictures</p>
|
||||
<p>rue de l'ale 1</p>
|
||||
<p>1003 Lausanne, Suisse</p>
|
||||
<p>Tél: +41 77 471 11 34</p>
|
||||
<p>Email: contact@cheapmo.ch</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Informations du destinataire -->
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold mb-4">Destinataire</h2>
|
||||
<div class="space-y-2" id="recipientInfo">
|
||||
<!-- Rempli dynamiquement -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<h2 class="text-xl font-semibold mb-4">Détails de la Facture</h2>
|
||||
<div class="space-y-4" id="invoiceDetails">
|
||||
<!-- Rempli dynamiquement -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 border-t pt-8">
|
||||
<h2 class="text-xl font-semibold mb-4">Informations de Paiement</h2>
|
||||
<div class="space-y-2">
|
||||
<p>Banque: Wise</p>
|
||||
<p>IBAN: BE22905094540247</p>
|
||||
<p>BIC/SWIFT: TRWIBEB1XXX</p>
|
||||
<p>Titulaire: Robin Szymczak</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/preview.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
80
static/preview.js
Normal file
80
static/preview.js
Normal file
@@ -0,0 +1,80 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Récupérer les données du formulaire depuis l'URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const formData = JSON.parse(decodeURIComponent(urlParams.get("data")));
|
||||
|
||||
// Afficher les informations du destinataire
|
||||
const recipientInfo = document.getElementById("recipientInfo");
|
||||
recipientInfo.innerHTML = `
|
||||
<p>${formData.recipient_name}</p>
|
||||
<p>${formData.recipient_address}</p>
|
||||
<p>${formData.recipient_postal_code} ${formData.recipient_town}</p>
|
||||
<p>${formData.recipient_country}</p>
|
||||
${
|
||||
formData.recipient_vat_number
|
||||
? `<p>TVA: ${formData.recipient_vat_number}</p>`
|
||||
: ""
|
||||
}
|
||||
`;
|
||||
|
||||
// Afficher les détails de la facture
|
||||
const invoiceDetails = document.getElementById("invoiceDetails");
|
||||
invoiceDetails.innerHTML = `
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p class="text-gray-600">Numéro de facture</p>
|
||||
<p class="font-semibold">${formData.invoice_number}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-gray-600">Date</p>
|
||||
<p class="font-semibold">${new Date().toLocaleDateString()}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-gray-600">Langue</p>
|
||||
<p class="font-semibold">${formData.language}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-gray-600">Montant</p>
|
||||
<p class="font-semibold">${formData.amount} ${
|
||||
formData.currency
|
||||
}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Gérer la validation de la facture
|
||||
document
|
||||
.getElementById("validateInvoice")
|
||||
.addEventListener("click", async function () {
|
||||
try {
|
||||
console.log("Envoi des données à l'API:", formData);
|
||||
|
||||
const response = await fetch("/api/invoices", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
console.log("Réponse reçue:", response.status);
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
console.log("Facture créée avec succès:", result);
|
||||
alert("Facture créée avec succès !");
|
||||
window.location.href = "/dashboard";
|
||||
} else {
|
||||
const error = await response.json();
|
||||
console.error("Erreur API:", error);
|
||||
alert(
|
||||
"Erreur lors de la création de la facture : " +
|
||||
(error.error || "Erreur inconnue")
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la requête:", error);
|
||||
alert("Erreur lors de la création de la facture : " + error.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
65
static/script.js
Normal file
65
static/script.js
Normal file
@@ -0,0 +1,65 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const form = document.getElementById("invoiceForm");
|
||||
|
||||
// Fonction pour afficher les messages
|
||||
function showMessage(message, type = "success") {
|
||||
const messageDiv = document.createElement("div");
|
||||
messageDiv.className = `${type}-message`;
|
||||
messageDiv.textContent = message;
|
||||
document.body.appendChild(messageDiv);
|
||||
|
||||
// Afficher le message
|
||||
setTimeout(() => messageDiv.classList.add("show"), 100);
|
||||
|
||||
// Supprimer le message après 3 secondes
|
||||
setTimeout(() => {
|
||||
messageDiv.classList.remove("show");
|
||||
setTimeout(() => messageDiv.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Fonction pour valider le formulaire
|
||||
function validateForm(formData) {
|
||||
const requiredFields = [
|
||||
"invoice_number",
|
||||
"amount",
|
||||
"recipient_name",
|
||||
"recipient_address",
|
||||
"recipient_postal_code",
|
||||
"recipient_town",
|
||||
"recipient_country",
|
||||
];
|
||||
|
||||
for (const field of requiredFields) {
|
||||
if (!formData.get(field)) {
|
||||
showMessage(`Le champ ${field} est requis`, "error");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Gestionnaire de soumission du formulaire
|
||||
form.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Récupérer les données du formulaire
|
||||
const formData = {
|
||||
language: form.language.value,
|
||||
invoice_number: form.invoice_number.value,
|
||||
amount: form.amount.value,
|
||||
currency: form.currency.value,
|
||||
recipient_name: form.recipient_name.value,
|
||||
recipient_address: form.recipient_address.value,
|
||||
recipient_postal_code: form.recipient_postal_code.value,
|
||||
recipient_town: form.recipient_town.value,
|
||||
recipient_country: form.recipient_country.value,
|
||||
recipient_vat_number: form.recipient_vat_number.value || null,
|
||||
};
|
||||
|
||||
// Rediriger vers la page de prévisualisation avec les données
|
||||
const queryString = encodeURIComponent(JSON.stringify(formData));
|
||||
window.location.href = `/preview?data=${queryString}`;
|
||||
});
|
||||
});
|
||||
52
static/styles.css
Normal file
52
static/styles.css
Normal file
@@ -0,0 +1,52 @@
|
||||
/* Styles personnalisés */
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
select {
|
||||
@apply border border-gray-300 rounded-md px-3 py-2;
|
||||
}
|
||||
|
||||
/* Animation de chargement */
|
||||
.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: -10px 0 0 -10px;
|
||||
border: 2px solid #ffffff;
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: loading 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Message de succès */
|
||||
.success-message {
|
||||
@apply fixed top-4 right-4 bg-green-500 text-white px-6 py-3 rounded-md shadow-lg transform transition-transform duration-300;
|
||||
transform: translateX(120%);
|
||||
}
|
||||
|
||||
.success-message.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* Message d'erreur */
|
||||
.error-message {
|
||||
@apply fixed top-4 right-4 bg-red-500 text-white px-6 py-3 rounded-md shadow-lg transform transition-transform duration-300;
|
||||
transform: translateX(120%);
|
||||
}
|
||||
|
||||
.error-message.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
Reference in New Issue
Block a user