rooting-future / templates /dashboard_simple.html
mtornani's picture
Initial HF Spaces deployment (clean branch without large binaries)
38f9c15
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rooting Future - Dashboard</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0f172a;
color: #f1f5f9;
min-height: 100vh;
}
.header {
background: #1e293b;
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #334155;
}
.logo { font-size: 1.25rem; font-weight: 700; color: #10b981; }
.nav a { color: #94a3b8; text-decoration: none; margin-left: 1.5rem; }
.nav a:hover { color: #fff; }
.main { display: flex; height: calc(100vh - 60px); }
.left-panel { width: 50%; padding: 1.5rem; overflow-y: auto; border-right: 1px solid #334155; }
.right-panel { width: 50%; display: flex; flex-direction: column; background: #0a0f1a; }
.console-header { padding: 1rem; background: #1e293b; display: flex; justify-content: space-between; align-items: center; }
.console-title { color: #10b981; font-weight: 600; }
.console-body { flex: 1; overflow-y: auto; padding: 1rem; font-family: 'Consolas', monospace; font-size: 0.85rem; }
.log-entry { padding: 0.25rem 0; border-bottom: 1px solid #1e293b; }
.log-time { color: #64748b; margin-right: 0.5rem; }
.log-level { padding: 0.1rem 0.4rem; border-radius: 3px; margin-right: 0.5rem; font-size: 0.75rem; }
.log-level.INFO { background: #3b82f6; }
.log-level.WARNING { background: #f59e0b; }
.log-level.ERROR { background: #ef4444; }
.card { background: #1e293b; border-radius: 8px; padding: 1.5rem; margin-bottom: 1rem; }
.card h3 { color: #10b981; margin-bottom: 1rem; }
.stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem; }
.stat-card { background: #1e293b; padding: 1rem; border-radius: 8px; text-align: center; }
.stat-value { font-size: 2rem; font-weight: 700; color: #10b981; }
.upload-zone { border: 2px dashed #334155; border-radius: 8px; padding: 2rem; text-align: center; cursor: pointer; transition: all 0.2s; }
.upload-zone:hover, .upload-zone.dragover { border-color: #10b981; background: #1e293b; }
.file-item { display: flex; justify-content: space-between; align-items: center; padding: 0.5rem; background: #334155; border-radius: 4px; margin-bottom: 0.5rem; }
.form-group { margin-bottom: 1rem; }
.form-group label { display: block; margin-bottom: 0.5rem; color: #94a3b8; }
.form-group input, .form-group select { width: 100%; padding: 0.75rem; background: #334155; border: 1px solid #475569; border-radius: 6px; color: #f1f5f9; }
.btn { padding: 0.75rem 1.5rem; border: none; border-radius: 6px; cursor: pointer; font-weight: 600; transition: all 0.2s; }
.btn-primary { background: #10b981; color: white; }
.btn-primary:disabled { background: #475569; cursor: not-allowed; opacity: 0.7; }
.btn-secondary { background: #334155; color: #f1f5f9; }
</style>
</head>
<body>
<header class="header">
<div class="logo">Rooting Future v6.0.9</div>
<nav class="nav">
<a href="/">Dashboard</a>
<a href="{{ url_for('plans_list') }}">Piani</a>
{% if user.role != 'user' %}
<a href="{{ url_for('new_plan') }}">Nuovo</a>
<a href="{{ url_for('review_queue') }}">Review</a>
{% endif %}
{% if user and user.is_authenticated %}
<span style="color: #64748b; margin-left: 1rem; font-size: 0.9rem;">{{ user.email }}</span>
<a href="{{ url_for('auth.logout') }}" style="color: #ef4444;">Esci</a>
{% endif %}
</nav>
</header>
<div class="main">
<div class="left-panel">
<div class="stats-grid">
<div class="stat-card"><div class="stat-value">{{ stats.total_plans }}</div><div class="stat-label">Piani Totali</div></div>
<div class="stat-card"><div class="stat-value">{{ stats.by_status.get('draft', 0) }}</div><div class="stat-label">Bozze</div></div>
<div class="stat-card"><div class="stat-value">{{ stats.sections_needing_review }}</div><div class="stat-label">Review</div></div>
<div class="stat-card"><div class="stat-value">{{ stats.average_credibility }}%</div><div class="stat-label">Credibilità</div></div>
</div>
<div class="card">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h3 style="margin:0;">Ingestion Stakeholder</h3>
<button type="button" id="toggleModeBtn" class="btn btn-secondary" style="font-size: 0.7rem; padding: 4px 8px;">
Passa a Folder Mode
</button>
</div>
<form id="uploadForm">
<div id="standardUpload">
<div class="upload-zone" id="dropZone">
<div class="upload-icon">📁</div>
<div>Trascina i file DOCX o uno <strong>ZIP</strong> qui</div>
<input type="file" id="fileInput" accept=".docx,.zip" multiple hidden>
</div>
</div>
<div id="technicalUpload" style="display:none;">
<div class="upload-zone" id="folderDropZone" style="border-color: #818cf8;">
<div class="upload-icon">📂</div>
<div>Carica Intera Cartella (Technical)</div>
<input type="file" id="folderInput" webkitdirectory directory multiple hidden>
</div>
</div>
<div class="file-list" id="fileList" style="margin-top:10px;"></div>
<div id="initialActions" style="margin-top:1rem;">
<div class="form-group">
<label>Nome Club / Azienda</label>
<div style="display: flex; gap: 10px;">
<input type="text" id="clubName" placeholder="Es: AC Rimini 1912" style="flex: 1;">
<button type="button" class="btn btn-secondary" id="searchWebBtn" style="white-space: nowrap;">🔍 Cerca sul Web</button>
</div>
</div>
<div class="form-group">
<label>🏆 Categoria / Campionato</label>
<select id="category" style="border-left: 4px solid #10b981;">
<option value="">-- Seleziona Categoria --</option>
{% for cat in categories %}
<option value="{{ cat }}">{{ cat }}</option>
{% endfor %}
</select>
</div>
<button type="button" class="btn btn-primary" id="mainAnalyzeBtn" style="width: 100%; height: 50px;">
🚀 Carica Documenti ed Analizza
</button>
</div>
<div id="loadingIndicator" style="display:none; text-align:center; padding: 20px; background: rgba(16, 185, 129, 0.1); border-radius: 12px; margin-top: 20px;">
<div style="font-size: 2rem; animation: spin 2s linear infinite;"></div>
<p style="margin-top: 10px; color: #10b981; font-weight: bold;">Analisi in background...</p>
<small style="color: #94a3b8;">Il sistema sta analizzando i file. Guarda i log a destra per i dettagli.</small>
</div>
<style> @keyframes spin { 100% { transform: rotate(360deg); } } </style>
<div id="uploadResults" style="display:none; margin-top: 20px;">
<div style='background:#1e293b; padding:20px; border-radius:12px; border: 1px solid #334155; margin-bottom: 20px;'>
<h3 style='color:#10b981; margin-top:0; border-bottom:1px solid #334155; padding-bottom:10px;'>🏛️ Anagrafica Società</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 15px;">
<div class="form-group"><label style="font-size:0.75rem;">Stadio</label><input type="text" id="stadiumConfirmed" style="background:#0f172a;"></div>
<div class="form-group"><label style="font-size:0.75rem;">Anno Fondazione</label><input type="number" id="foundationConfirmed" style="background:#0f172a;"></div>
<div class="form-group"><label style="font-size:0.75rem;">Presidente</label><input type="text" id="presidentConfirmed" style="background:#0f172a;"></div>
<div class="form-group"><label style="font-size:0.75rem;">Budget Stimato</label><input type="text" id="budgetConfirmed" style="background:#0f172a;"></div>
</div>
</div>
<div id="alignmentDashboardContainer"></div>
<button type="button" class="btn btn-primary" id="finalGenerateBtn" style="width: 100%; margin-top: 20px; height: 60px; font-size: 1.1rem; background: linear-gradient(to right, #10b981, #059669);">
💎 Conferma e Genera Progetto Finale
</button>
</div>
</form>
</div>
</div>
<div class="right-panel">
<div class="console-header"><div class="console-title">Live Console</div></div>
<div class="console-body" id="logConsole"></div>
</div>
</div>
<script>
const logConsole = document.getElementById('logConsole');
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const folderDropZone = document.getElementById('folderDropZone');
const folderInput = document.getElementById('folderInput');
const fileList = document.getElementById('fileList');
const mainAnalyzeBtn = document.getElementById('mainAnalyzeBtn');
const finalGenerateBtn = document.getElementById('finalGenerateBtn');
const uploadResults = document.getElementById('uploadResults');
const initialActions = document.getElementById('initialActions');
const loadingIndicator = document.getElementById('loadingIndicator');
const dashboardContainer = document.getElementById('alignmentDashboardContainer');
const toggleModeBtn = document.getElementById('toggleModeBtn');
const searchWebBtn = document.getElementById('searchWebBtn');
const standardUpload = document.getElementById('standardUpload');
const technicalUpload = document.getElementById('technicalUpload');
let selectedFiles = [];
let lastExtractedData = null;
let isTechnicalMode = false;
let displayedLogs = new Set();
toggleModeBtn.onclick = () => {
isTechnicalMode = !isTechnicalMode;
standardUpload.style.display = isTechnicalMode ? 'none' : 'block';
technicalUpload.style.display = isTechnicalMode ? 'block' : 'none';
toggleModeBtn.textContent = isTechnicalMode ? 'Standard Mode' : 'Folder Mode';
selectedFiles = [];
renderFiles();
};
if (searchWebBtn) {
searchWebBtn.onclick = async () => {
const name = document.getElementById('clubName').value;
if (!name) { alert("Inserisci il nome del club."); return; }
searchWebBtn.disabled = true;
searchWebBtn.innerHTML = "⏳ Ricerca in corso...";
addLog('INFO', "Avvio ricerca anagrafica web per: " + name);
try {
const resp = await fetch('/api/research/basic-info', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ club_name: name })
});
const data = await resp.json();
if (data.success) {
uploadResults.style.display = 'block';
document.getElementById('stadiumConfirmed').value = data.info.stadium || "";
document.getElementById('foundationConfirmed').value = data.info.foundation_year || "";
document.getElementById('presidentConfirmed').value = data.info.president || "";
document.getElementById('budgetConfirmed').value = data.info.budget || "";
addLog('INFO', "✅ Dati club recuperati con successo dal web.");
} else {
addLog('WARNING', "Nessun dato trovato per questo club.");
alert("Ricerca completata: nessun dato anagrafico trovato. Inseriscili manualmente.");
}
} catch (e) {
addLog('ERROR', "Errore durante la ricerca web: " + e);
} finally {
searchWebBtn.disabled = false;
searchWebBtn.innerHTML = "🔍 Cerca sul Web";
}
};
}
function addLog(level, message) {
const div = document.createElement('div');
div.className = 'log-entry';
div.innerHTML = '<span class="log-time">' + new Date().toLocaleTimeString() + '</span><span class="log-level ' + level + '">' + level + '</span><span>' + message + '</span>';
logConsole.appendChild(div);
logConsole.scrollTop = logConsole.scrollHeight;
}
if (dropZone) dropZone.onclick = () => fileInput.click();
if (folderDropZone) folderDropZone.onclick = () => folderInput.click();
fileInput.onchange = (e) => handleFiles(e.target.files);
folderInput.onchange = (e) => handleFiles(e.target.files);
function handleFiles(files) {
for (const f of files) { if (f.name.toLowerCase().endsWith('.docx') || f.name.toLowerCase().endsWith('.zip')) { if (f.name.endsWith('.zip')) selectedFiles = [f]; else selectedFiles.push(f); } }
renderFiles();
}
function renderFiles() {
fileList.innerHTML = selectedFiles.map((f, i) => '<div class="file-item"><span>' + (f.webkitRelativePath || f.name) + '</span><button type="button" onclick="selectedFiles.splice(' + i + ',1);renderFiles();">X</button></div>').join('');
mainAnalyzeBtn.disabled = (selectedFiles.length === 0);
}
mainAnalyzeBtn.onclick = async () => {
mainAnalyzeBtn.disabled = true;
loadingIndicator.style.display = 'block';
initialActions.style.display = 'none';
standardUpload.style.display = 'none';
technicalUpload.style.display = 'none';
const formData = new FormData();
selectedFiles.forEach(f => formData.append('files[]', f, f.webkitRelativePath || f.name));
formData.append('club_name', document.getElementById('clubName').value);
try {
const resp = await fetch('/api/upload-docx', { method: 'POST', body: formData });
const data = await resp.json();
if (data.success) {
// Inizia il polling per i risultati dell'analisi
pollAnalysisResults(data.project_id);
} else { alert(data.error); resetUI(); }
} catch (e) { resetUI(); }
};
async function pollAnalysisResults(projectId) {
const poll = setInterval(async () => {
try {
const resp = await fetch('/api/generation-status/' + projectId);
const data = await resp.json();
if (data.status === 'processing') {
// Aggiorna HUD
loadingIndicator.querySelector('p').textContent = data.message;
loadingIndicator.querySelector('small').textContent = 'Progresso: ' + data.progress + '%';
// Effetto visivo sulla barra (opzionale se volessimo aggiungerla)
if (data.progress > 0) {
addLog('INFO', data.message + ' (' + data.progress + '%)');
}
} else if (data.status === 'completed') {
clearInterval(poll);
loadingIndicator.style.display = 'none';
uploadResults.style.display = 'block';
// Qui carichiamo i dati finali
const finalResp = await fetch('/api/check-analysis/' + projectId);
const finalData = await finalResp.json();
dashboardContainer.innerHTML = finalData.alignment_dashboard;
document.getElementById('stadiumConfirmed').value = finalData.extracted_data.stadium || document.getElementById('stadiumConfirmed').value;
lastExtractedData = finalData.extracted_data;
addLog('INFO', 'Analisi completata con successo!');
} else if (data.status === 'error') {
clearInterval(poll);
alert(data.message);
resetUI();
addLog('ERROR', data.message);
}
} catch (e) {
console.error("Polling error:", e);
}
}, 2000);
}
function resetUI() {
loadingIndicator.style.display = 'none';
initialActions.style.display = 'block';
mainAnalyzeBtn.disabled = false;
}
finalGenerateBtn.onclick = async () => {
finalGenerateBtn.disabled = true;
finalGenerateBtn.textContent = 'Generazione...';
const resp = await fetch('/api/generate-from-webhook', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
club_name: lastExtractedData.club_name,
category: document.getElementById('category').value,
project_id: lastExtractedData.project_id,
stakeholders_inputs: lastExtractedData.stakeholders,
stadium: document.getElementById('stadiumConfirmed').value,
foundation_year: document.getElementById('foundationConfirmed').value,
president: document.getElementById('presidentConfirmed').value,
budget: document.getElementById('budgetConfirmed').value,
request_mode: 'production'
})
});
const data = await resp.json();
if (data.success) window.location.href = '/success/' + data.plan_id;
else { alert(data.error); finalGenerateBtn.disabled = false; }
};
setInterval(async () => {
try {
const resp = await fetch('/api/stream/logs');
const data = await resp.json();
if (data.success && data.logs) {
data.logs.forEach(l => {
const id = l.timestamp + l.message;
if (!displayedLogs.has(id)) { addLog(l.level, l.message); displayedLogs.add(id); }
});
}
} catch(e) { }
}, 2000);
</script>
</body>
</html>