Initial commit
This commit is contained in:
commit
983cee0320
322 changed files with 57174 additions and 0 deletions
320
backup/first -fina app/app/templates/dashboard.html
Executable file
320
backup/first -fina app/app/templates/dashboard.html
Executable file
|
|
@ -0,0 +1,320 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Dashboard - FINA{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="dashboard">
|
||||
|
||||
{% if categories and categories|length > 0 %}
|
||||
|
||||
<div class="metrics-section glass-card" style="margin-bottom: 2rem; margin-top: 0;">
|
||||
<div class="metrics-header">
|
||||
<h2>📈 {{ _('dashboard.metrics') }}</h2>
|
||||
<div class="metrics-controls">
|
||||
<select id="metricCategory" class="metric-select">
|
||||
<option value="all">{{ _('dashboard.all_categories') }}</option>
|
||||
{% for cat in categories %}
|
||||
<option value="{{ cat.id }}" data-color="{{ cat.color }}">{{ cat.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select id="metricYear" class="metric-select">
|
||||
{% for year in available_years %}
|
||||
<option value="{{ year }}" {% if year == current_year %}selected{% endif %}>{{ year }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="charts-container">
|
||||
<div class="chart-box chart-box-pie">
|
||||
<h3>{{ _('dashboard.expenses_by_category') }}</h3>
|
||||
<div class="chart-wrapper">
|
||||
<canvas id="pieChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-box chart-box-bar">
|
||||
<h3>{{ _('dashboard.monthly_expenses') }}</h3>
|
||||
<div class="chart-wrapper">
|
||||
<canvas id="barChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-container">
|
||||
<div class="glass-card stat-card">
|
||||
<h3>{{ _('dashboard.total_spent') }}</h3>
|
||||
<p class="stat-value">{{ total_spent|currency }}</p>
|
||||
</div>
|
||||
|
||||
<div class="glass-card stat-card">
|
||||
<h3>{{ _('dashboard.categories_section') }}</h3>
|
||||
<p class="stat-value">{{ (categories|default([]))|length }}</p>
|
||||
</div>
|
||||
|
||||
<div class="glass-card stat-card">
|
||||
<h3>{{ _('dashboard.total_expenses') }}</h3>
|
||||
<p class="stat-value">{{ total_expenses }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-header" style="margin-top: 2rem;">
|
||||
<h2>📁 {{ _('dashboard.categories_section') }}</h2>
|
||||
</div>
|
||||
<div class="categories-grid">
|
||||
{% for category in categories %}
|
||||
<a href="{{ url_for('main.view_category', category_id=category.id) }}" class="category-card glass-card" style="border-left: 4px solid {{ category.color }}">
|
||||
<div class="category-content">
|
||||
<h3>{{ category.name }}</h3>
|
||||
{% if category.description %}
|
||||
<p class="category-description">{{ category.description }}</p>
|
||||
{% endif %}
|
||||
<div class="category-amount">{{ category.get_total_spent()|currency }}</div>
|
||||
<div class="category-info">
|
||||
<span>{{ category.expenses|length }} {{ _('category.expenses') | lower }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
|
||||
<div class="stats-container">
|
||||
<div class="glass-card stat-card">
|
||||
<h3>{{ _('dashboard.total_spent') }}</h3>
|
||||
<p class="stat-value">{{ total_spent|currency }}</p>
|
||||
</div>
|
||||
|
||||
<div class="glass-card stat-card">
|
||||
<h3>{{ _('category.expenses') }}</h3>
|
||||
<p class="stat-value">{{ (categories|default([]))|length }}</p>
|
||||
</div>
|
||||
|
||||
<div class="glass-card stat-card">
|
||||
<h3>{{ _('dashboard.total_expenses') }}</h3>
|
||||
<p class="stat-value">{{ total_expenses }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glass-card empty-state">
|
||||
<h2>{{ _('empty.welcome_title') }}</h2>
|
||||
<p>{{ _('empty.welcome_message') }}</p>
|
||||
<a href="{{ url_for('main.create_category') }}" class="btn btn-primary">{{ _('empty.create_category') }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Upcoming Subscriptions Widget -->
|
||||
{% if upcoming_subscriptions or suggestions_count > 0 %}
|
||||
<div class="glass-card" style="margin-top: 2rem;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
||||
<h2>🔄 {{ _('subscription.title') }}</h2>
|
||||
<a href="{{ url_for('subscriptions.index') }}" class="btn btn-secondary btn-sm">{{ _('dashboard.view_all') }}</a>
|
||||
</div>
|
||||
|
||||
{% if suggestions_count > 0 %}
|
||||
<div class="alert" style="background: rgba(245, 158, 11, 0.2); border: 1px solid #f59e0b; margin-bottom: 1rem; position: relative;">
|
||||
💡 <strong>{{ suggestions_count }}</strong> {{ _('subscription.suggestions') | lower }} -
|
||||
<a href="{{ url_for('subscriptions.index') }}" style="color: #fbbf24; text-decoration: underline;">{{ _('common.view') }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if upcoming_subscriptions %}
|
||||
<div class="subscriptions-widget">
|
||||
{% for sub in upcoming_subscriptions %}
|
||||
<div class="subscription-widget-item" style="display: flex; justify-content: space-between; padding: 0.75rem 0; border-bottom: 1px solid var(--glass-border);">
|
||||
<div>
|
||||
<strong>{{ sub.name }}</strong>
|
||||
<br>
|
||||
<small style="color: var(--text-secondary);">
|
||||
{{ sub.amount|currency }} -
|
||||
{% if sub.next_due_date %}
|
||||
{% set days_until = (sub.next_due_date - today).days %}
|
||||
{% if days_until == 0 %}
|
||||
{{ _('subscription.today') }}
|
||||
{% elif days_until == 1 %}
|
||||
{{ _('subscription.tomorrow') }}
|
||||
{% elif days_until < 7 %}
|
||||
in {{ days_until }} {{ _('subscription.days') }}
|
||||
{% else %}
|
||||
{{ sub.next_due_date.strftime('%b %d') }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
<span style="font-weight: 600;">{{ sub.amount|currency }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p style="color: var(--text-secondary); text-align: center; padding: 1rem;">
|
||||
{{ _('subscription.no_upcoming') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.charts-container {
|
||||
display: grid;
|
||||
grid-template-columns: 400px 1fr;
|
||||
gap: 2rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.chart-box-pie {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chart-box-bar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
position: relative;
|
||||
height: 400px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chart-box-pie .chart-wrapper {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.charts-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.chart-box-pie .chart-wrapper {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
const chartData = {{ chart_data|tojson }};
|
||||
const categories = {{ categories_json|tojson }};
|
||||
const currentYear = {{ current_year }};
|
||||
const currencySymbol = '{{ current_user.currency }}';
|
||||
let pieChart, barChart;
|
||||
|
||||
function initCharts() {
|
||||
const pieCtx = document.getElementById('pieChart').getContext('2d');
|
||||
pieChart = new Chart(pieCtx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: chartData.map(d => d.name),
|
||||
datasets: [{
|
||||
data: chartData.map(d => d.value),
|
||||
backgroundColor: chartData.map(d => d.color),
|
||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||
borderWidth: 2
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
aspectRatio: 1,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: { color: '#ffffff', padding: 15, font: { size: 12 } }
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
return context.label + ': ' + formatCurrency(context.parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const barCtx = document.getElementById('barChart').getContext('2d');
|
||||
barChart = new Chart(barCtx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
datasets: []
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
aspectRatio: 2,
|
||||
scales: {
|
||||
y: { beginAtZero: true, ticks: { color: '#ffffff' }, grid: { color: 'rgba(255, 255, 255, 0.1)' } },
|
||||
x: { ticks: { color: '#ffffff' }, grid: { color: 'rgba(255, 255, 255, 0.1)' } }
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
return formatCurrency(context.parsed.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
updateCharts('all', currentYear);
|
||||
}
|
||||
|
||||
function formatCurrency(amount) {
|
||||
const formatted = amount.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
|
||||
if (currencySymbol === 'RON') {
|
||||
return formatted + ' Lei';
|
||||
} else if (currencySymbol === 'EUR') {
|
||||
return '€' + formatted;
|
||||
} else if (currencySymbol === 'GBP') {
|
||||
return '£' + formatted;
|
||||
} else {
|
||||
return '$' + formatted;
|
||||
}
|
||||
}
|
||||
|
||||
function updateCharts(categoryId, year) {
|
||||
fetch(`/api/metrics?category=${categoryId}&year=${year}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
barChart.data.datasets = [{
|
||||
label: data.category_name,
|
||||
data: data.monthly_data,
|
||||
backgroundColor: data.color || '#6366f1',
|
||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||
borderWidth: 1
|
||||
}];
|
||||
barChart.update();
|
||||
|
||||
if (categoryId === 'all') {
|
||||
pieChart.data.labels = data.pie_labels;
|
||||
pieChart.data.datasets[0].data = data.pie_data;
|
||||
pieChart.data.datasets[0].backgroundColor = data.pie_colors;
|
||||
pieChart.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('metricCategory').addEventListener('change', function() {
|
||||
updateCharts(this.value, document.getElementById('metricYear').value);
|
||||
});
|
||||
|
||||
document.getElementById('metricYear').addEventListener('change', function() {
|
||||
updateCharts(document.getElementById('metricCategory').value, this.value);
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initCharts);
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue