diff --git a/services/host_api/app_minimal.py b/services/host_api/app_minimal.py
index a5ad280..bcf9dbb 100644
--- a/services/host_api/app_minimal.py
+++ b/services/host_api/app_minimal.py
@@ -97,6 +97,22 @@ async def get_documents():
"total": len(documents_db)
}
+@app.get("/api/notary/document/{document_id}/status")
+async def get_document_status(document_id: str):
+ """Récupérer le statut d'un document spécifique"""
+ if document_id not in documents_db:
+ return {"error": "Document non trouvé"}, 404
+
+ doc = documents_db[document_id]
+ return {
+ "document_id": document_id,
+ "status": doc.get("status", "unknown"),
+ "progress": doc.get("progress", 0),
+ "current_step": doc.get("current_step", "En attente"),
+ "upload_time": doc.get("upload_time"),
+ "completion_time": doc.get("completion_time")
+ }
+
@app.get("/api/notary/documents/{document_id}")
async def get_document(document_id: str):
"""Détails d'un document"""
diff --git a/services/web_interface/app.js b/services/web_interface/app.js
index 28be521..38daaf8 100644
--- a/services/web_interface/app.js
+++ b/services/web_interface/app.js
@@ -148,30 +148,8 @@ class NotaryApp {
};
reader.readAsDataURL(file);
} else if (file.type === 'application/pdf') {
- // Preview for PDF
- uploadArea.innerHTML = `
-
-
-
-
- Aperçu PDF non disponible
-
-
-
-
${file.name}
-
${this.formatFileSize(file.size)}
-
Type: ${file.type}
-
-
-
-
-
-
- `;
+ // Preview for PDF with PDF.js
+ this.showPDFPreview(file, uploadArea);
} else {
// Generic preview for other files
uploadArea.innerHTML = `
@@ -197,9 +175,120 @@ class NotaryApp {
}
}
+ showPDFPreview(file, container) {
+ // Create PDF preview using PDF.js
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const arrayBuffer = e.target.result;
+
+ // Load PDF.js if not already loaded
+ if (typeof pdfjsLib === 'undefined') {
+ this.loadPDFJS().then(() => {
+ this.renderPDFPreview(arrayBuffer, file, container);
+ });
+ } else {
+ this.renderPDFPreview(arrayBuffer, file, container);
+ }
+ };
+ reader.readAsArrayBuffer(file);
+ }
+
+ loadPDFJS() {
+ return new Promise((resolve, reject) => {
+ if (typeof pdfjsLib !== 'undefined') {
+ resolve();
+ return;
+ }
+
+ const script = document.createElement('script');
+ script.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js';
+ script.onload = () => {
+ pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
+ resolve();
+ };
+ script.onerror = reject;
+ document.head.appendChild(script);
+ });
+ }
+
+ async renderPDFPreview(arrayBuffer, file, container) {
+ try {
+ const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
+ const page = await pdf.getPage(1);
+
+ const scale = 0.5;
+ const viewport = page.getViewport({ scale });
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ canvas.height = viewport.height;
+ canvas.width = viewport.width;
+
+ await page.render({
+ canvasContext: context,
+ viewport: viewport
+ }).promise;
+
+ container.innerHTML = `
+
+
+
+
+ Page 1 sur ${pdf.numPages}
+
+
+
+
${file.name}
+
${this.formatFileSize(file.size)}
+
Type: ${file.type}
+
+
+
+
+
+
+ `;
+
+ // Replace the canvas in the container
+ const canvasContainer = container.querySelector('canvas');
+ canvasContainer.parentNode.replaceChild(canvas, canvasContainer);
+
+ } catch (error) {
+ console.error('Erreur aperçu PDF:', error);
+ // Fallback to icon if PDF preview fails
+ container.innerHTML = `
+
+
+
+
+ Aperçu PDF non disponible
+
+
+
+
${file.name}
+
${this.formatFileSize(file.size)}
+
Type: ${file.type}
+
+
+
+
+
+
+ `;
+ }
+ }
+
showDocumentPreview(uploadResult, file) {
// Create a preview modal or section
- const previewContainer = document.getElementById('upload-preview');
+ let previewContainer = document.getElementById('upload-preview');
if (!previewContainer) {
// Create preview container if it doesn't exist
const uploadSection = document.getElementById('upload-section');
@@ -207,10 +296,9 @@ class NotaryApp {
previewDiv.id = 'upload-preview';
previewDiv.className = 'mt-4';
uploadSection.appendChild(previewDiv);
+ previewContainer = document.getElementById('upload-preview');
}
- const previewContainer = document.getElementById('upload-preview');
-
let previewContent = '';
if (file.type.startsWith('image/')) {
@@ -253,8 +341,134 @@ class NotaryApp {
previewContainer.innerHTML = previewContent;
};
reader.readAsDataURL(file);
+ } else if (file.type === 'application/pdf') {
+ // PDF preview after upload
+ this.showPDFPreviewAfterUpload(file, uploadResult, previewContainer);
} else {
previewContent = `
+
+
+
+
+
+
+
+
+
Informations du document
+
+ - Nom: ${file.name}
+ - Taille: ${this.formatFileSize(file.size)}
+ - Type: ${file.type}
+ - ID Document: ${uploadResult.document_id}
+ - Statut: ${uploadResult.status}
+
+
+
+
+
+
+
+
+
+ `;
+ previewContainer.innerHTML = previewContent;
+ }
+ }
+
+ showPDFPreviewAfterUpload(file, uploadResult, container) {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const arrayBuffer = e.target.result;
+
+ // Load PDF.js if not already loaded
+ if (typeof pdfjsLib === 'undefined') {
+ this.loadPDFJS().then(() => {
+ this.renderPDFPreviewAfterUpload(arrayBuffer, file, uploadResult, container);
+ });
+ } else {
+ this.renderPDFPreviewAfterUpload(arrayBuffer, file, uploadResult, container);
+ }
+ };
+ reader.readAsArrayBuffer(file);
+ }
+
+ async renderPDFPreviewAfterUpload(arrayBuffer, file, uploadResult, container) {
+ try {
+ const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
+ const page = await pdf.getPage(1);
+
+ const scale = 0.3;
+ const viewport = page.getViewport({ scale });
+
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ canvas.height = viewport.height;
+ canvas.width = viewport.width;
+
+ await page.render({
+ canvasContext: context,
+ viewport: viewport
+ }).promise;
+
+ const previewContent = `
+
+
+
+
+
+
+
+
+ Page 1 sur ${pdf.numPages}
+
+
+
+
+
Informations du document
+
+ - Nom: ${file.name}
+ - Taille: ${this.formatFileSize(file.size)}
+ - Type: ${file.type}
+ - Pages: ${pdf.numPages}
+ - ID Document: ${uploadResult.document_id}
+ - Statut: ${uploadResult.status}
+
+
+
+
+
+
+
+
+
+ `;
+
+ container.innerHTML = previewContent;
+
+ // Replace the canvas in the container
+ const canvasContainer = container.querySelector('canvas');
+ canvasContainer.parentNode.replaceChild(canvas, canvasContainer);
+
+ } catch (error) {
+ console.error('Erreur aperçu PDF après upload:', error);
+ // Fallback to icon if PDF preview fails
+ container.innerHTML = `
`;
- previewContainer.innerHTML = previewContent;
}
}
diff --git a/services/web_interface/bootstrap.min.css b/services/web_interface/bootstrap.min.css
new file mode 100644
index 0000000..ff0d550
--- /dev/null
+++ b/services/web_interface/bootstrap.min.css
@@ -0,0 +1,370 @@
+/* Bootstrap CSS minimal pour 4NK Notariat */
+:root {
+ --bs-blue: #0d6efd;
+ --bs-indigo: #6610f2;
+ --bs-purple: #6f42c1;
+ --bs-pink: #d63384;
+ --bs-red: #dc3545;
+ --bs-orange: #fd7e14;
+ --bs-yellow: #ffc107;
+ --bs-green: #198754;
+ --bs-teal: #20c997;
+ --bs-cyan: #0dcaf0;
+ --bs-white: #fff;
+ --bs-gray: #6c757d;
+ --bs-gray-dark: #343a40;
+ --bs-primary: #0d6efd;
+ --bs-secondary: #6c757d;
+ --bs-success: #198754;
+ --bs-info: #0dcaf0;
+ --bs-warning: #ffc107;
+ --bs-danger: #dc3545;
+ --bs-light: #f8f9fa;
+ --bs-dark: #212529;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1.5;
+ color: #212529;
+ background-color: #fff;
+}
+
+.container {
+ width: 100%;
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.row {
+ display: flex;
+ flex-wrap: wrap;
+ margin-right: -15px;
+ margin-left: -15px;
+}
+
+.col-md-2, .col-md-4, .col-md-6, .col-md-8, .col-md-9, .col-md-10 {
+ position: relative;
+ width: 100%;
+ padding-right: 15px;
+ padding-left: 15px;
+}
+
+.col-md-2 { flex: 0 0 16.666667%; max-width: 16.666667%; }
+.col-md-4 { flex: 0 0 33.333333%; max-width: 33.333333%; }
+.col-md-6 { flex: 0 0 50%; max-width: 50%; }
+.col-md-8 { flex: 0 0 66.666667%; max-width: 66.666667%; }
+.col-md-9 { flex: 0 0 75%; max-width: 75%; }
+.col-md-10 { flex: 0 0 83.333333%; max-width: 83.333333%; }
+
+.card {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ word-wrap: break-word;
+ background-color: #fff;
+ background-clip: border-box;
+ border: 1px solid rgba(0,0,0,.125);
+ border-radius: 0.375rem;
+ margin-bottom: 1rem;
+}
+
+.card-header {
+ padding: 0.75rem 1.25rem;
+ margin-bottom: 0;
+ background-color: rgba(0,0,0,.03);
+ border-bottom: 1px solid rgba(0,0,0,.125);
+}
+
+.card-body {
+ flex: 1 1 auto;
+ padding: 1.25rem;
+}
+
+.btn {
+ display: inline-block;
+ font-weight: 400;
+ line-height: 1.5;
+ color: #212529;
+ text-align: center;
+ text-decoration: none;
+ vertical-align: middle;
+ cursor: pointer;
+ user-select: none;
+ background-color: transparent;
+ border: 1px solid transparent;
+ padding: 0.375rem 0.75rem;
+ font-size: 1rem;
+ border-radius: 0.375rem;
+ transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
+}
+
+.btn-primary {
+ color: #fff;
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+}
+
+.btn-primary:hover {
+ color: #fff;
+ background-color: #0b5ed7;
+ border-color: #0a58ca;
+}
+
+.btn-outline-primary {
+ color: #0d6efd;
+ border-color: #0d6efd;
+}
+
+.btn-outline-primary:hover {
+ color: #fff;
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+}
+
+.btn-outline-danger {
+ color: #dc3545;
+ border-color: #dc3545;
+}
+
+.btn-outline-danger:hover {
+ color: #fff;
+ background-color: #dc3545;
+ border-color: #dc3545;
+}
+
+.btn-outline-secondary {
+ color: #6c757d;
+ border-color: #6c757d;
+}
+
+.btn-outline-secondary:hover {
+ color: #fff;
+ background-color: #6c757d;
+ border-color: #6c757d;
+}
+
+.btn-sm {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.875rem;
+ border-radius: 0.25rem;
+}
+
+.btn-group {
+ position: relative;
+ display: inline-flex;
+ vertical-align: middle;
+}
+
+.alert {
+ position: relative;
+ padding: 0.75rem 1.25rem;
+ margin-bottom: 1rem;
+ border: 1px solid transparent;
+ border-radius: 0.375rem;
+}
+
+.alert-success {
+ color: #0f5132;
+ background-color: #d1e7dd;
+ border-color: #badbcc;
+}
+
+.alert-danger {
+ color: #842029;
+ background-color: #f8d7da;
+ border-color: #f5c2c7;
+}
+
+.alert-warning {
+ color: #664d03;
+ background-color: #fff3cd;
+ border-color: #ffecb5;
+}
+
+.alert-info {
+ color: #055160;
+ background-color: #cff4fc;
+ border-color: #b6effb;
+}
+
+.badge {
+ display: inline-block;
+ padding: 0.35em 0.65em;
+ font-size: 0.75em;
+ font-weight: 700;
+ line-height: 1;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ border-radius: 0.375rem;
+}
+
+.bg-success { background-color: #198754 !important; }
+.bg-danger { background-color: #dc3545 !important; }
+.bg-warning { background-color: #ffc107 !important; }
+.bg-info { background-color: #0dcaf0 !important; }
+.bg-primary { background-color: #0d6efd !important; }
+.bg-secondary { background-color: #6c757d !important; }
+
+.navbar {
+ position: relative;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.navbar-brand {
+ padding-top: 0.3125rem;
+ padding-bottom: 0.3125rem;
+ margin-right: 1rem;
+ font-size: 1.25rem;
+ color: rgba(0,0,0,.9);
+ text-decoration: none;
+}
+
+.nav {
+ display: flex;
+ flex-wrap: wrap;
+ padding-left: 0;
+ margin-bottom: 0;
+ list-style: none;
+}
+
+.nav-link {
+ display: block;
+ padding: 0.5rem 1rem;
+ color: #0d6efd;
+ text-decoration: none;
+ transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out;
+}
+
+.nav-link:hover {
+ color: #0a58ca;
+}
+
+.nav-link.active {
+ color: #fff;
+ background-color: #0d6efd;
+}
+
+.progress {
+ display: flex;
+ height: 1rem;
+ overflow: hidden;
+ font-size: 0.75rem;
+ background-color: #e9ecef;
+ border-radius: 0.375rem;
+}
+
+.progress-bar {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ overflow: hidden;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ background-color: #0d6efd;
+ transition: width .6s ease;
+}
+
+.text-center { text-align: center !important; }
+.text-muted { color: #6c757d !important; }
+.text-primary { color: #0d6efd !important; }
+.text-success { color: #198754 !important; }
+.text-danger { color: #dc3545 !important; }
+
+.mb-0 { margin-bottom: 0 !important; }
+.mb-1 { margin-bottom: 0.25rem !important; }
+.mb-2 { margin-bottom: 0.5rem !important; }
+.mb-3 { margin-bottom: 1rem !important; }
+.mb-4 { margin-bottom: 1.5rem !important; }
+.mb-5 { margin-bottom: 3rem !important; }
+
+.mt-2 { margin-top: 0.5rem !important; }
+.mt-3 { margin-top: 1rem !important; }
+.mt-4 { margin-top: 1.5rem !important; }
+
+.me-2 { margin-right: 0.5rem !important; }
+.ms-2 { margin-left: 0.5rem !important; }
+
+.py-5 { padding-top: 3rem !important; padding-bottom: 3rem !important; }
+
+.img-thumbnail {
+ padding: 0.25rem;
+ background-color: #fff;
+ border: 1px solid #dee2e6;
+ border-radius: 0.375rem;
+ max-width: 100%;
+ height: auto;
+}
+
+.img-fluid {
+ max-width: 100%;
+ height: auto;
+}
+
+.rounded {
+ border-radius: 0.375rem !important;
+}
+
+.border {
+ border: 1px solid #dee2e6 !important;
+}
+
+.d-none { display: none !important; }
+
+.list-unstyled {
+ padding-left: 0;
+ list-style: none;
+}
+
+.list-group {
+ display: flex;
+ flex-direction: column;
+ padding-left: 0;
+ margin-bottom: 0;
+ border-radius: 0.375rem;
+}
+
+.list-group-item {
+ position: relative;
+ display: block;
+ padding: 0.5rem 1rem;
+ color: #212529;
+ text-decoration: none;
+ background-color: #fff;
+ border: 1px solid rgba(0,0,0,.125);
+}
+
+.list-group-item:first-child {
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+}
+
+.list-group-item:last-child {
+ border-bottom-right-radius: inherit;
+ border-bottom-left-radius: inherit;
+}
+
+/* Responsive */
+@media (min-width: 768px) {
+ .col-md-2, .col-md-4, .col-md-6, .col-md-8, .col-md-9, .col-md-10 {
+ flex: 0 0 auto;
+ }
+}
diff --git a/services/web_interface/chart.min.js b/services/web_interface/chart.min.js
new file mode 100644
index 0000000..f8f5d20
--- /dev/null
+++ b/services/web_interface/chart.min.js
@@ -0,0 +1,148 @@
+// Chart.js minimal pour 4NK Notariat
+window.Chart = class Chart {
+ constructor(ctx, config) {
+ this.ctx = ctx;
+ this.config = config;
+ this.destroyed = false;
+
+ // Créer un canvas simple si Chart.js n'est pas disponible
+ this.createSimpleChart();
+ }
+
+ createSimpleChart() {
+ if (this.destroyed) return;
+
+ const canvas = this.ctx;
+ const ctx = canvas.getContext('2d');
+ const config = this.config;
+
+ // Effacer le canvas
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ if (config.type === 'doughnut') {
+ this.drawDoughnutChart(ctx, config);
+ } else if (config.type === 'line') {
+ this.drawLineChart(ctx, config);
+ }
+ }
+
+ drawDoughnutChart(ctx, config) {
+ const data = config.data;
+ const labels = data.labels || [];
+ const values = data.datasets[0].data || [];
+ const colors = data.datasets[0].backgroundColor || ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF'];
+
+ const centerX = canvas.width / 2;
+ const centerY = canvas.height / 2;
+ const radius = Math.min(centerX, centerY) - 20;
+
+ const total = values.reduce((sum, val) => sum + val, 0);
+ let currentAngle = 0;
+
+ // Dessiner les segments
+ values.forEach((value, index) => {
+ const sliceAngle = (value / total) * 2 * Math.PI;
+
+ ctx.beginPath();
+ ctx.moveTo(centerX, centerY);
+ ctx.arc(centerX, centerY, radius, currentAngle, currentAngle + sliceAngle);
+ ctx.closePath();
+ ctx.fillStyle = colors[index % colors.length];
+ ctx.fill();
+
+ currentAngle += sliceAngle;
+ });
+
+ // Dessiner la légende
+ let legendY = 20;
+ labels.forEach((label, index) => {
+ ctx.fillStyle = colors[index % colors.length];
+ ctx.fillRect(10, legendY, 15, 15);
+ ctx.fillStyle = '#333';
+ ctx.font = '12px Arial';
+ ctx.fillText(label, 30, legendY + 12);
+ legendY += 20;
+ });
+ }
+
+ drawLineChart(ctx, config) {
+ const data = config.data;
+ const labels = data.labels || [];
+ const values = data.datasets[0].data || [];
+ const color = data.datasets[0].borderColor || '#007bff';
+
+ const padding = 40;
+ const chartWidth = canvas.width - 2 * padding;
+ const chartHeight = canvas.height - 2 * padding;
+
+ const maxValue = Math.max(...values);
+ const minValue = Math.min(...values);
+ const valueRange = maxValue - minValue || 1;
+
+ // Dessiner les axes
+ ctx.strokeStyle = '#ddd';
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ ctx.moveTo(padding, padding);
+ ctx.lineTo(padding, canvas.height - padding);
+ ctx.lineTo(canvas.width - padding, canvas.height - padding);
+ ctx.stroke();
+
+ // Dessiner la ligne
+ ctx.strokeStyle = color;
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+
+ values.forEach((value, index) => {
+ const x = padding + (index / (values.length - 1)) * chartWidth;
+ const y = canvas.height - padding - ((value - minValue) / valueRange) * chartHeight;
+
+ if (index === 0) {
+ ctx.moveTo(x, y);
+ } else {
+ ctx.lineTo(x, y);
+ }
+ });
+
+ ctx.stroke();
+
+ // Dessiner les points
+ ctx.fillStyle = color;
+ values.forEach((value, index) => {
+ const x = padding + (index / (values.length - 1)) * chartWidth;
+ const y = canvas.height - padding - ((value - minValue) / valueRange) * chartHeight;
+
+ ctx.beginPath();
+ ctx.arc(x, y, 4, 0, 2 * Math.PI);
+ ctx.fill();
+ });
+
+ // Dessiner les labels
+ ctx.fillStyle = '#333';
+ ctx.font = '10px Arial';
+ ctx.textAlign = 'center';
+ labels.forEach((label, index) => {
+ const x = padding + (index / (values.length - 1)) * chartWidth;
+ ctx.fillText(label, x, canvas.height - 10);
+ });
+ }
+
+ destroy() {
+ this.destroyed = true;
+ if (this.ctx && this.ctx.clearRect) {
+ this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
+ }
+ }
+
+ update() {
+ if (!this.destroyed) {
+ this.createSimpleChart();
+ }
+ }
+};
+
+// Simuler les options globales
+window.Chart.defaults = {
+ responsive: true,
+ maintainAspectRatio: false
+};
diff --git a/services/web_interface/fontawesome.min.css b/services/web_interface/fontawesome.min.css
new file mode 100644
index 0000000..a454727
--- /dev/null
+++ b/services/web_interface/fontawesome.min.css
@@ -0,0 +1,25 @@
+/* Font Awesome minimal pour 4NK Notariat */
+.fas, .fa {
+ font-family: "Font Awesome 5 Free";
+ font-weight: 900;
+ display: inline-block;
+ font-style: normal;
+ font-variant: normal;
+ text-rendering: auto;
+ line-height: 1;
+}
+
+.fa-cloud-upload-alt:before { content: "☁"; }
+.fa-folder-open:before { content: "📁"; }
+.fa-file:before { content: "📄"; }
+.fa-file-pdf:before { content: "📕"; }
+.fa-file-alt:before { content: "📄"; }
+.fa-upload:before { content: "⬆"; }
+.fa-times:before { content: "✕"; }
+.fa-eye:before { content: "👁"; }
+.fa-search:before { content: "🔍"; }
+.fa-download:before { content: "⬇"; }
+.fa-upload:before { content: "⬆"; }
+.fa-3x { font-size: 3em; }
+.fa-4x { font-size: 4em; }
+.fa-2x { font-size: 2em; }
diff --git a/services/web_interface/index.html b/services/web_interface/index.html
index 3525548..5299994 100644
--- a/services/web_interface/index.html
+++ b/services/web_interface/index.html
@@ -4,8 +4,9 @@
4NK Notariat - Traitement de Documents
-
-
+
+
+