201 lines
12 KiB
HTML
Executable File
201 lines
12 KiB
HTML
Executable File
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>📊 Dépenses Dashboard</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;600;700&display=swap" rel="stylesheet">
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<style>
|
|
:root { --bg-dark: #0d0d1a; --bg-card: #16162a; --bg-input: #1a1a35; --primary: #e94560; --secondary: #7b2cbf; --accent: #00d9ff; --text: #fff; --text-dim: #888; }
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: 'Outfit', sans-serif; background: var(--bg-dark); color: var(--text); padding: 15px; }
|
|
h1 { text-align: center; font-size: 1.6em; background: linear-gradient(135deg, var(--primary), var(--secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
|
.nav { display: flex; gap: 10px; margin: 15px 0; }
|
|
.nav a { flex: 1; padding: 12px; background: var(--bg-card); border-radius: 10px; text-align: center; color: var(--text-dim); text-decoration: none; }
|
|
.nav a.active { background: linear-gradient(135deg, var(--primary), var(--secondary)); color: #fff; }
|
|
.card { background: var(--bg-card); border-radius: 15px; padding: 15px; margin-bottom: 15px; }
|
|
h2 { font-size: 1em; margin-bottom: 12px; color: var(--accent); }
|
|
.filter-bar { display: flex; gap: 10px; margin-bottom: 15px; flex-wrap: wrap; }
|
|
.filter-chip { padding: 10px 20px; background: var(--bg-input); border-radius: 25px; font-size: 0.9em; cursor: pointer; }
|
|
.filter-chip.active { background: var(--primary); color: #fff; }
|
|
.chart-container { height: 250px; position: relative; }
|
|
.total { text-align: right; font-size: 1.3em; margin: 15px 0; padding: 15px; background: rgba(0,217,255,0.1); border-radius: 10px; }
|
|
|
|
.home-btn{position:fixed;top:10px;left:10px;z-index:9999;background:linear-gradient(135deg,#00d9ff,#7b2cbf);color:#fff;border:none;border-radius:8px;padding:10px 15px;font-size:14px;cursor:pointer;text-decoration:none;display:flex;align-items:center;gap:8px;box-shadow:0 2px 10px rgba(0,217,255,0.3);transition:all 0.3s;font-family:-apple-system,BlinkMacSystemFont,sans-serif}.home-btn:hover{transform:translateY(-2px);box-shadow:0 4px 15px rgba(0,217,255,0.5)}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<a href="https://portal-kolifee.duckdns.org/" class="home-btn" title="Retour au portail"><span style="font-size:18px">🏠</span><span>Accueil</span></a>
|
|
<h1>📊 Dépenses Dashboard</h1>
|
|
<div class="nav">
|
|
<a href="/?page=saisie" id="nav-saisie">✏️ Saisie</a>
|
|
<a href="dashboard" id="nav-dashboard" class="active">📊 Dashboard</a>
|
|
<a href="dashboard?page=config" id="nav-config">⚙️ Config</a>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>💰 Total: <span id="total-display">0.00€</span></h2>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>📊 Filtres</h2>
|
|
<div class="filter-bar" id="filter-bar">
|
|
<div class="filter-chip active" onclick="setFilter('month')">Mois</div>
|
|
<div class="filter-chip" onclick="setFilter('person')">Personne</div>
|
|
<div class="filter-chip" onclick="setFilter('category')">Catégorie</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>📈 Graphiques</h2>
|
|
<div class="filter-bar" id="chart-filters">
|
|
<div class="filter-chip active" onclick="setChartMode('bar')">Bar chart</div>
|
|
<div class="filter-chip" onclick="setChartMode('pie')">Camembert</div>
|
|
</div>
|
|
<div class="chart-container">
|
|
<canvas id="mainChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
var depenses = [];
|
|
var currentFilter = { type: 'month', value: null };
|
|
var chartMode = 'bar';
|
|
var chart = null;
|
|
|
|
function getFilteredData() {
|
|
if (!currentFilter.value) return depenses;
|
|
if (currentFilter.type === 'month') return depenses.filter(d => d.date && d.date.startsWith(currentFilter.value));
|
|
if (currentFilter.type === 'person') return depenses.filter(d => d.prenom === currentFilter.value);
|
|
if (currentFilter.type === 'category') return depenses.filter(d => (d.category || 'Autre') === currentFilter.value);
|
|
return depenses;
|
|
}
|
|
|
|
function setFilter(type) {
|
|
currentFilter.type = type;
|
|
currentFilter.value = null;
|
|
setChartMode('bar');
|
|
|
|
var values = [];
|
|
if (type === 'month') values = [...new Set(depenses.map(d => d.date.split('-')[0] + '-' + d.date.split('-')[1]))].sort().reverse();
|
|
else if (type === 'person') values = [...new Set(depenses.map(d => d.prenom))].sort();
|
|
else if (type === 'category') values = [...new Set(depenses.map(d => d.category || 'Autre'))].sort();
|
|
|
|
currentFilter.value = values[0] || null;
|
|
renderFilters();
|
|
updateChart();
|
|
}
|
|
|
|
function renderFilters() {
|
|
var html = '<div class="filter-chip' + (!currentFilter.value ? ' active' : '') + '" onclick="setFilter(\'' + currentFilter.type + '\')">Tous</div>';
|
|
|
|
var values = [];
|
|
if (currentFilter.type === 'month') values = [...new Set(depenses.map(d => d.date.split('-')[0] + '-' + d.date.split('-')[1]))].sort().reverse();
|
|
else if (currentFilter.type === 'person') values = [...new Set(depenses.map(d => d.prenom))].sort();
|
|
else if (currentFilter.type === 'category') values = [...new Set(depenses.map(d => d.category || 'Autre'))].sort();
|
|
|
|
for (var i = 0; i < values.length; i++) {
|
|
html += '<div class="filter-chip' + (currentFilter.value === values[i] ? ' active' : '') + '" onclick="currentFilter.value = \'' + values[i] + '\'; renderFilters(); updateChart();">' + values[i] + '</div>';
|
|
}
|
|
document.getElementById('filter-bar').innerHTML = html;
|
|
}
|
|
|
|
function setChartMode(mode) {
|
|
chartMode = mode;
|
|
document.getElementById('chart-filters').innerHTML = '<div class="filter-chip' + (mode === 'bar' ? ' active' : '') + '" onclick="setChartMode(\'bar\')">Bar chart</div><div class="filter-chip' + (mode === 'pie' ? ' active' : '') + '" onclick="setChartMode(\'pie\')">Camembert</div>';
|
|
updateChart();
|
|
}
|
|
|
|
function updateSummaries() {
|
|
var filtered = getFilteredData();
|
|
var cats = {};
|
|
var pers = {};
|
|
filtered.forEach(function(d) {
|
|
var c = d.category || 'Autre';
|
|
var p = d.prenom || 'Inconnu';
|
|
cats[c] = (cats[c] || 0) + (parseFloat(d.montant) || 0);
|
|
pers[p] = (pers[p] || 0) + (parseFloat(d.montant) || 0);
|
|
});
|
|
var catHtml = '<table style="width:100%;text-align:left"><tr><th>Catégorie</th><th>Total</th></tr>';
|
|
Object.keys(cats).sort().forEach(function(c) { catHtml += '<tr><td>'+c+'</td><td style="color:var(--primary)">'+cats[c].toFixed(2)+'€</td></tr>'; });
|
|
catHtml += '</table>';
|
|
document.getElementById("category-summary").innerHTML = catHtml;
|
|
var persHtml = '<table style="width:100%;text-align:left"><tr><th>Personne</th><th>Total</th></tr>';
|
|
Object.keys(pers).sort().forEach(function(p) { persHtml += '<tr><td>'+p+'</td><td style="color:var(--secondary)">'+pers[p].toFixed(2)+'€</td></tr>'; });
|
|
persHtml += '</table>';
|
|
document.getElementById("person-summary").innerHTML = persHtml;
|
|
}
|
|
|
|
function updateChart() {
|
|
var ctx = document.getElementById('mainChart').getContext('2d');
|
|
var filtered = getFilteredData();
|
|
var labels = [];
|
|
var data = [];
|
|
var backgroundColors = [];
|
|
|
|
if (currentFilter.type === 'month') {
|
|
var months = {};
|
|
filtered.forEach(function(d) { var m = d.date.split('-')[0] + '-' + d.date.split('-')[1]; months[m] = (months[m] || 0) + (parseFloat(d.montant) || 0); });
|
|
Object.keys(months).sort().reverse().forEach(function(m) { labels.push(m); data.push(months[m]); backgroundColors.push('rgba(0, 217, 255, 0.7)'); });
|
|
} else if (currentFilter.type === 'person') {
|
|
var persons = {};
|
|
filtered.forEach(function(d) { var p = d.prenom || 'Inconnu'; persons[p] = (persons[p] || 0) + (parseFloat(d.montant) || 0); });
|
|
Object.keys(persons).sort().forEach(function(p) { labels.push(p); data.push(persons[p]); backgroundColors.push('rgba(233, 69, 96, 0.7)'); });
|
|
} else if (currentFilter.type === 'category') {
|
|
var categories = {};
|
|
filtered.forEach(function(d) { var c = d.category || 'Autre'; categories[c] = (categories[c] || 0) + (parseFloat(d.montant) || 0); });
|
|
var colors = ['rgba(0, 217, 255, 0.7)', 'rgba(233, 69, 96, 0.7)', 'rgba(123, 44, 191, 0.7)', 'rgba(0, 255, 136, 0.7)', 'rgba(255, 200, 0, 0.7)'];
|
|
Object.keys(categories).sort().forEach(function(c, i) { labels.push(c); data.push(categories[c]); backgroundColors.push(colors[i % colors.length]); });
|
|
}
|
|
|
|
var total = filtered.reduce(function(s, d) { return s + (parseFloat(d.montant) || 0); }, 0);
|
|
document.getElementById('total-display').textContent = total.toFixed(2) + '€';
|
|
|
|
if (chart) chart.destroy();
|
|
// Category chart
|
|
var catCtx = document.getElementById('categoryChart');
|
|
if (catCtx) {
|
|
var catData = {};
|
|
filtered.forEach(function(d) { var c = d.category || 'Autre'; catData[c] = (catData[c] || 0) + (parseFloat(d.montant) || 0); });
|
|
var catLabels = Object.keys(catData).sort();
|
|
var catValues = catLabels.map(function(c) { return catData[c]; });
|
|
var catColors = ['rgba(0, 217, 255, 0.7)', 'rgba(233, 69, 96, 0.7)', 'rgba(123, 44, 191, 0.7)', 'rgba(0, 255, 136, 0.7)', 'rgba(255, 200, 0, 0.7)', 'rgba(255, 99, 132, 0.7)'];
|
|
new Chart(catCtx, {
|
|
type: 'doughnut',
|
|
data: { labels: catLabels, datasets: [{ data: catValues, backgroundColor: catColors }] },
|
|
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { color: '#fff' } } } }
|
|
});
|
|
}
|
|
|
|
updateSummaries(); chart = new Chart(ctx, {
|
|
type: chartMode,
|
|
data: { labels: labels, datasets: [{ label: 'Montant (€)', data: data, backgroundColor: backgroundColors, borderColor: backgroundColors.map(c => c.replace('0.7', '1')), borderWidth: 1 }] },
|
|
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { color: '#fff' } } }, scales: chartMode === 'pie' ? {} : { y: { beginAtZero: true, grid: { color: 'rgba(255,255,255,0.1)' }, ticks: { color: '#888' } }, x: { grid: { display: false }, ticks: { color: '#888' } } } }
|
|
});
|
|
}
|
|
|
|
async function load() {
|
|
try {
|
|
var r = await fetch('api/depenses');
|
|
depenses = await r.json();
|
|
setFilter('month');
|
|
} catch (e) {
|
|
console.error('Erreur:', e);
|
|
document.getElementById('total-display').textContent = 'Erreur de chargement';
|
|
}
|
|
}
|
|
|
|
load();
|
|
</script>
|
|
<div class="card">
|
|
<h2>📊 Par Catégorie</h2>
|
|
<div class="chart-container" style="height:200px">
|
|
<canvas id="categoryChart"></canvas>
|
|
</div>
|
|
<div id="category-summary"></div>
|
|
</div>
|
|
<div class="card"><h2>👤 Par Utilisateur</h2><div id="person-summary"></div></div>
|
|
</body>
|
|
</html>
|