Initial commit
This commit is contained in:
commit
983cee0320
322 changed files with 57174 additions and 0 deletions
211
app/templates/admin.html
Normal file
211
app/templates/admin.html
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin Panel - FINA{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex h-screen w-full">
|
||||
<!-- Side Navigation -->
|
||||
<aside id="sidebar" class="hidden lg:flex w-64 flex-col bg-sidebar-light dark:bg-background-dark border-r border-border-light dark:border-[#233648] transition-all duration-300 shadow-sm dark:shadow-none">
|
||||
<div class="p-6 flex flex-col h-full justify-between">
|
||||
<div class="flex flex-col gap-8">
|
||||
<!-- User Profile -->
|
||||
<div class="flex gap-3 items-center">
|
||||
<img src="{{ current_user.avatar | avatar_url }}" alt="{{ current_user.username }}" class="size-10 rounded-full border-2 border-primary/30 object-cover">
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-text-main dark:text-white text-base font-bold leading-none">{{ current_user.username }}</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-xs font-normal mt-1">
|
||||
{% if current_user.is_admin %}<span data-translate="user.admin">Admin</span>{% else %}<span data-translate="user.user">User</span>{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Links -->
|
||||
<nav class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.dashboard') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">dashboard</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.dashboard">Dashboard</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.transactions') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">receipt_long</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.transactions">Transactions</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.income') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">payments</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.income">Income</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.recurring') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">repeat</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.recurring">Recurring</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.import_page') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">file_upload</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.import">Import CSV</span>
|
||||
</a> <a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.reports') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">pie_chart</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.reports">Reports</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.documents') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">folder_open</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.documents">Documents</span>
|
||||
</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-primary/10 text-primary border border-primary/10" href="{{ url_for('main.admin') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">admin_panel_settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.admin">Admin</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Links -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<button id="theme-toggle" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]" id="theme-icon">light_mode</span>
|
||||
<span class="text-sm font-medium" id="theme-text" data-translate="dashboard.lightMode">Light Mode</span>
|
||||
</button>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.settings') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.settings">Settings</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('auth.logout') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">logout</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.logout">Log out</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative">
|
||||
<!-- Top Header -->
|
||||
<header class="h-16 flex items-center justify-between px-6 lg:px-8 border-b border-border-light dark:border-[#233648] bg-white/80 dark:bg-background-dark/95 backdrop-blur z-10 shrink-0 shadow-sm dark:shadow-none">
|
||||
<div class="flex items-center gap-4">
|
||||
<button id="menu-toggle" class="lg:hidden text-text-main dark:text-white">
|
||||
<span class="material-symbols-outlined">menu</span>
|
||||
</button>
|
||||
<div>
|
||||
<h2 class="text-text-main dark:text-white text-lg font-bold" data-translate="admin.title">Admin Panel</h2>
|
||||
<p class="text-text-muted dark:text-slate-400 text-xs mt-0.5" data-translate="admin.subtitle">Manage users and system settings</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Content Area -->
|
||||
<div class="flex-1 overflow-y-auto bg-background-light dark:bg-card-dark">
|
||||
<div class="container mx-auto px-4 py-6 max-w-7xl">
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-slate-700 rounded-xl p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-text-muted dark:text-slate-400 text-sm" data-translate="admin.totalUsers">Total Users</p>
|
||||
<p id="total-users" class="text-2xl font-bold text-text-main dark:text-white mt-1">-</p>
|
||||
</div>
|
||||
<span class="material-symbols-outlined text-primary text-[40px]">group</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-slate-700 rounded-xl p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-text-muted dark:text-slate-400 text-sm" data-translate="admin.adminUsers">Admin Users</p>
|
||||
<p id="admin-users" class="text-2xl font-bold text-text-main dark:text-white mt-1">-</p>
|
||||
</div>
|
||||
<span class="material-symbols-outlined text-blue-500 text-[40px]">shield_person</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-slate-700 rounded-xl p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-text-muted dark:text-slate-400 text-sm" data-translate="admin.twoFAEnabled">2FA Enabled</p>
|
||||
<p id="twofa-users" class="text-2xl font-bold text-text-main dark:text-white mt-1">-</p>
|
||||
</div>
|
||||
<span class="material-symbols-outlined text-green-500 text-[40px]">verified_user</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Users Table -->
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-slate-700 rounded-xl overflow-hidden">
|
||||
<div class="p-6 border-b border-border-light dark:border-slate-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold text-text-main dark:text-white" data-translate="admin.users">Users</h2>
|
||||
<button onclick="openCreateUserModal()" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 transition-colors">
|
||||
<span class="material-symbols-outlined text-[18px]">add</span>
|
||||
<span data-translate="admin.createUser">Create User</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-background-light dark:bg-slate-800/50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted dark:text-slate-400 uppercase tracking-wider" data-translate="admin.username">Username</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted dark:text-slate-400 uppercase tracking-wider" data-translate="admin.email">Email</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted dark:text-slate-400 uppercase tracking-wider" data-translate="admin.role">Role</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted dark:text-slate-400 uppercase tracking-wider" data-translate="admin.twoFA">2FA</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted dark:text-slate-400 uppercase tracking-wider" data-translate="admin.language">Language</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted dark:text-slate-400 uppercase tracking-wider" data-translate="admin.currency">Currency</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted dark:text-slate-400 uppercase tracking-wider" data-translate="admin.joined">Joined</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted dark:text-slate-400 uppercase tracking-wider" data-translate="admin.actions">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="users-table" class="divide-y divide-border-light dark:divide-slate-700">
|
||||
<!-- Users will be populated here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Create User Modal -->
|
||||
<div id="create-user-modal" class="fixed inset-0 bg-black/50 backdrop-blur-sm hidden items-center justify-center z-50 p-4">
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-slate-700 rounded-xl max-w-md w-full p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-xl font-bold text-text-main dark:text-white" data-translate="admin.createNewUser">Create New User</h3>
|
||||
<button onclick="closeCreateUserModal()" class="text-text-muted dark:text-slate-400 hover:text-text-main dark:hover:text-white">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="create-user-form" class="space-y-4">
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-slate-400 text-sm mb-2 block" data-translate="form.username">Username</label>
|
||||
<input type="text" name="username" required class="w-full bg-background-light dark:bg-slate-800 border border-border-light dark:border-slate-700 rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-slate-400 text-sm mb-2 block" data-translate="form.email">Email</label>
|
||||
<input type="email" name="email" required class="w-full bg-background-light dark:bg-slate-800 border border-border-light dark:border-slate-700 rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-slate-400 text-sm mb-2 block" data-translate="form.password">Password</label>
|
||||
<input type="password" name="password" required minlength="8" class="w-full bg-background-light dark:bg-slate-800 border border-border-light dark:border-slate-700 rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="checkbox" name="is_admin" id="is-admin-checkbox" class="rounded border-border-light dark:border-slate-700 text-primary focus:ring-primary">
|
||||
<label for="is-admin-checkbox" class="text-text-main dark:text-white text-sm" data-translate="admin.makeAdmin">Make admin</label>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 pt-2">
|
||||
<button type="submit" class="flex-1 bg-primary hover:bg-primary/90 text-white py-2 rounded-lg font-medium transition-colors">
|
||||
<span data-translate="admin.create">Create</span>
|
||||
</button>
|
||||
<button type="button" onclick="closeCreateUserModal()" class="flex-1 bg-background-light dark:bg-slate-800 text-text-main dark:text-white py-2 rounded-lg font-medium hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors">
|
||||
<span data-translate="common.cancel">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/admin.js') }}"></script>
|
||||
{% endblock %}
|
||||
126
app/templates/auth/backup_codes.html
Normal file
126
app/templates/auth/backup_codes.html
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Backup Codes - FINA{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen flex items-center justify-center p-4 bg-background-light dark:bg-background-dark">
|
||||
<div class="max-w-2xl w-full">
|
||||
<!-- Success Header -->
|
||||
<div class="text-center mb-8">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 bg-green-100 dark:bg-green-500/20 rounded-full mb-4">
|
||||
<span class="material-symbols-outlined text-green-600 dark:text-green-400 text-[32px]">verified_user</span>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-text-main dark:text-white mb-2" data-translate="twofa.setupSuccess">Two-Factor Authentication Enabled!</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9]" data-translate="twofa.backupCodesDesc">Save these backup codes in a secure location</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-2xl p-6 md:p-8 shadow-sm">
|
||||
<!-- Warning Alert -->
|
||||
<div class="bg-yellow-50 dark:bg-yellow-500/10 border border-yellow-200 dark:border-yellow-500/30 rounded-lg p-4 mb-6">
|
||||
<div class="flex gap-3">
|
||||
<span class="material-symbols-outlined text-yellow-600 dark:text-yellow-400 text-[20px] flex-shrink-0">warning</span>
|
||||
<div class="flex-1">
|
||||
<h3 class="font-semibold text-yellow-800 dark:text-yellow-400 mb-1" data-translate="twofa.important">Important!</h3>
|
||||
<p class="text-sm text-yellow-700 dark:text-yellow-300" data-translate="twofa.backupCodesWarning">Each backup code can only be used once. Store them securely - you'll need them if you lose access to your authenticator app.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backup Codes Grid -->
|
||||
<div class="bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-xl p-6 mb-6">
|
||||
<h3 class="text-sm font-medium text-text-muted dark:text-[#92adc9] uppercase tracking-wide mb-4" data-translate="twofa.yourBackupCodes">Your Backup Codes</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{% for code in backup_codes %}
|
||||
<div class="flex items-center gap-3 bg-card-light dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-lg p-3">
|
||||
<span class="text-text-muted dark:text-[#92adc9] text-sm font-medium">{{ loop.index }}.</span>
|
||||
<code class="flex-1 text-primary font-mono font-bold text-base tracking-wider">{{ code }}</code>
|
||||
<button onclick="copyCode('{{ code }}')" class="text-text-muted dark:text-[#92adc9] hover:text-primary transition-colors" title="Copy">
|
||||
<span class="material-symbols-outlined text-[20px]">content_copy</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<a href="{{ url_for('auth.download_backup_codes_pdf') }}" class="flex-1 inline-flex items-center justify-center gap-2 px-6 py-3 bg-primary text-white rounded-lg font-medium hover:bg-primary/90 transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]">download</span>
|
||||
<span data-translate="twofa.downloadPDF">Download as PDF</span>
|
||||
</a>
|
||||
<button onclick="printCodes()" class="flex-1 inline-flex items-center justify-center gap-2 px-6 py-3 bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] text-text-main dark:text-white rounded-lg font-medium hover:bg-slate-100 dark:hover:bg-white/5 transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]">print</span>
|
||||
<span data-translate="twofa.print">Print Codes</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<a href="{{ url_for('main.settings') }}" class="inline-flex items-center gap-2 text-text-muted dark:text-[#92adc9] hover:text-primary transition-colors text-sm font-medium">
|
||||
<span data-translate="twofa.continueToSettings">Continue to Settings</span>
|
||||
<span class="material-symbols-outlined text-[16px]">arrow_forward</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Section -->
|
||||
<div class="mt-6 bg-blue-50 dark:bg-blue-500/10 border border-blue-200 dark:border-blue-500/30 rounded-lg p-4">
|
||||
<div class="flex gap-3">
|
||||
<span class="material-symbols-outlined text-blue-600 dark:text-blue-400 text-[20px] flex-shrink-0">info</span>
|
||||
<div class="text-sm text-blue-700 dark:text-blue-300">
|
||||
<p class="font-medium mb-1" data-translate="twofa.howToUse">How to use backup codes:</p>
|
||||
<ul class="list-disc list-inside space-y-1 text-blue-600 dark:text-blue-400">
|
||||
<li data-translate="twofa.useWhen">Use a backup code when you can't access your authenticator app</li>
|
||||
<li data-translate="twofa.enterCode">Enter the code in the 2FA field when logging in</li>
|
||||
<li data-translate="twofa.oneTimeUse">Each code works only once - it will be deleted after use</li>
|
||||
<li data-translate="twofa.regenerate">You can regenerate codes anytime from Settings</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyCode(code) {
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
// Show success notification
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'fixed top-4 right-4 z-50 px-4 py-3 rounded-lg shadow-lg flex items-center gap-3 bg-green-500 text-white animate-slideIn';
|
||||
notification.innerHTML = `
|
||||
<span class="material-symbols-outlined text-[20px]">check_circle</span>
|
||||
<span class="text-sm font-medium">Code copied to clipboard!</span>
|
||||
`;
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.classList.add('animate-slideOut');
|
||||
setTimeout(() => document.body.removeChild(notification), 300);
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
function printCodes() {
|
||||
window.print();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@media print {
|
||||
body * {
|
||||
visibility: hidden;
|
||||
}
|
||||
.bg-card-light, .bg-card-dark {
|
||||
visibility: visible;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.bg-card-light *, .bg-card-dark * {
|
||||
visibility: visible;
|
||||
}
|
||||
button, .mt-6, .bg-blue-50, .dark\:bg-blue-500\/10 {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
257
app/templates/auth/login.html
Normal file
257
app/templates/auth/login.html
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Login - FINA{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<style>
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
|
||||
.radial-blue-bg {
|
||||
background: radial-gradient(circle, #1e3a8a 0%, #1e293b 50%, #0f172a 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.radial-blue-bg::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background:
|
||||
repeating-conic-gradient(from 0deg at 50% 50%,
|
||||
transparent 0deg,
|
||||
rgba(43, 140, 238, 0.1) 2deg,
|
||||
transparent 4deg);
|
||||
animation: rotate 60s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.login-input {
|
||||
border-radius: 25px;
|
||||
padding: 12px 20px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.light .login-input {
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.dark .login-input,
|
||||
:root:not(.light) .login-input {
|
||||
background: #1e293b;
|
||||
border: 1px solid #334155;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.login-input:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.light .login-input:focus {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.dark .login-input:focus,
|
||||
:root:not(.light) .login-input:focus {
|
||||
background: #1e293b;
|
||||
}
|
||||
|
||||
.light .login-input::placeholder {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.dark .login-input::placeholder,
|
||||
:root:not(.light) .login-input::placeholder {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.login-input:-webkit-autofill,
|
||||
.login-input:-webkit-autofill:hover,
|
||||
.login-input:-webkit-autofill:focus {
|
||||
transition: background-color 5000s ease-in-out 0s;
|
||||
}
|
||||
|
||||
.light .login-input:-webkit-autofill,
|
||||
.light .login-input:-webkit-autofill:hover,
|
||||
.light .login-input:-webkit-autofill:focus {
|
||||
-webkit-text-fill-color: #0f172a;
|
||||
-webkit-box-shadow: 0 0 0px 1000px #f8fafc inset;
|
||||
}
|
||||
|
||||
.dark .login-input:-webkit-autofill,
|
||||
.dark .login-input:-webkit-autofill:hover,
|
||||
.dark .login-input:-webkit-autofill:focus,
|
||||
:root:not(.light) .login-input:-webkit-autofill,
|
||||
:root:not(.light) .login-input:-webkit-autofill:hover,
|
||||
:root:not(.light) .login-input:-webkit-autofill:focus {
|
||||
-webkit-text-fill-color: #ffffff;
|
||||
-webkit-box-shadow: 0 0 0px 1000px #1e293b inset;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
padding: 12px 40px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
box-shadow: 0 0 20px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
|
||||
.login-btn:hover {
|
||||
background: #2563eb;
|
||||
transform: translateX(2px);
|
||||
box-shadow: 0 0 30px rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen flex">
|
||||
<!-- Left Side - Logo with Radial Background -->
|
||||
<div class="hidden lg:flex lg:w-1/2 radial-blue-bg items-center justify-center relative">
|
||||
<div class="relative z-10">
|
||||
<img src="{{ url_for('static', filename='icons/logo.png') }}" alt="FINA Logo" class="w-96 h-96 rounded-full shadow-2xl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Side - Login Form -->
|
||||
<div class="w-full lg:w-1/2 flex items-center justify-center bg-background-light dark:bg-slate-900 p-8">
|
||||
<div class="w-full max-w-md">
|
||||
<!-- Mobile Logo -->
|
||||
<div class="lg:hidden flex justify-center mb-8">
|
||||
<img src="{{ url_for('static', filename='icons/logo.png') }}" alt="FINA Logo" class="w-24 h-24 rounded-full shadow-lg">
|
||||
</div>
|
||||
|
||||
<!-- Login Header -->
|
||||
<h1 class="text-3xl font-bold text-text-main dark:text-white mb-8">Login Here!</h1>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form id="login-form" class="space-y-6">
|
||||
<!-- Username Field -->
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-icons text-text-muted dark:text-slate-400 text-[24px]">person</span>
|
||||
<input type="text" name="username" required autofocus
|
||||
class="login-input flex-1"
|
||||
placeholder="username or email">
|
||||
</div>
|
||||
|
||||
<!-- Password Field -->
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-icons text-text-muted dark:text-slate-400 text-[24px]">lock</span>
|
||||
<div class="flex-1 relative">
|
||||
<input type="password" name="password" id="password" required
|
||||
class="login-input w-full pr-12"
|
||||
placeholder="password">
|
||||
<button type="button" onclick="togglePassword()" class="absolute right-4 top-1/2 -translate-y-1/2 text-text-muted dark:text-slate-400 hover:text-primary dark:hover:text-blue-400">
|
||||
<span class="material-icons text-[20px]" id="eye-icon">visibility</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2FA Field (hidden by default) -->
|
||||
<div id="2fa-field" class="hidden flex items-center gap-3">
|
||||
<span class="material-icons text-text-muted dark:text-slate-400 text-[24px]">security</span>
|
||||
<input type="text" name="two_factor_code"
|
||||
class="login-input flex-1"
|
||||
placeholder="2FA code">
|
||||
</div>
|
||||
|
||||
<!-- Remember Password & Login Button -->
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="flex items-center text-sm text-text-muted dark:text-slate-300">
|
||||
<input type="checkbox" name="remember" id="remember" class="mr-2 rounded border-border-light dark:border-slate-500 bg-background-light dark:bg-slate-700 text-blue-600">
|
||||
<span>Remember Password</span>
|
||||
</label>
|
||||
|
||||
<button type="submit" class="login-btn">
|
||||
<span>LOGIN</span>
|
||||
<span class="material-icons text-[18px]">arrow_forward</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Register Link -->
|
||||
<div class="mt-8 text-center text-sm text-text-muted dark:text-slate-300">
|
||||
Don't have an account?
|
||||
<a href="{{ url_for('auth.register') }}" class="text-primary dark:text-blue-400 hover:text-primary/80 dark:hover:text-blue-300 hover:underline font-medium">Create your account <span class="underline">here</span>!</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function togglePassword() {
|
||||
const passwordInput = document.getElementById('password');
|
||||
const eyeIcon = document.getElementById('eye-icon');
|
||||
|
||||
if (passwordInput.type === 'password') {
|
||||
passwordInput.type = 'text';
|
||||
eyeIcon.textContent = 'visibility_off';
|
||||
} else {
|
||||
passwordInput.type = 'password';
|
||||
eyeIcon.textContent = 'visibility';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('login-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const data = Object.fromEntries(formData);
|
||||
|
||||
try {
|
||||
const response = await fetch('/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.requires_2fa) {
|
||||
document.getElementById('2fa-field').classList.remove('hidden');
|
||||
showToast('Please enter your 2FA code', 'info');
|
||||
} else if (result.success) {
|
||||
window.location.href = result.redirect;
|
||||
} else {
|
||||
showToast(result.message || 'Login failed', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('An error occurred', 'error');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
93
app/templates/auth/register.html
Normal file
93
app/templates/auth/register.html
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Register - FINA{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen flex items-center justify-center p-4 bg-background-light dark:bg-background-dark">
|
||||
<div class="max-w-md w-full">
|
||||
<!-- Logo/Brand -->
|
||||
<div class="text-center mb-8">
|
||||
<img src="{{ url_for('static', filename='icons/logo.png') }}" alt="FINA Logo" class="w-32 h-32 mx-auto mb-4 rounded-full shadow-lg shadow-primary/30">
|
||||
<h1 class="text-4xl font-bold text-text-main dark:text-white mb-2">FINA</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9]" data-translate="register.tagline">Start managing your finances today</p>
|
||||
</div>
|
||||
|
||||
<!-- Register Form -->
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-2xl p-8">
|
||||
<h2 class="text-2xl font-bold text-text-main dark:text-white mb-6" data-translate="register.title">Create Account</h2>
|
||||
|
||||
<form id="register-form" class="space-y-4">
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.username">Username</label>
|
||||
<input type="text" name="username" required class="w-full bg-background-light dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.email">Email</label>
|
||||
<input type="email" name="email" required class="w-full bg-background-light dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.password">Password</label>
|
||||
<input type="password" name="password" required minlength="8" class="w-full bg-background-light dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.language">Language</label>
|
||||
<select name="language" class="w-full bg-background-light dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
<option value="en">English</option>
|
||||
<option value="ro">Română</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.currency">Currency</label>
|
||||
<select name="currency" class="w-full bg-background-light dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
<option value="USD">USD ($)</option>
|
||||
<option value="EUR">EUR (€)</option>
|
||||
<option value="GBP">GBP (£)</option>
|
||||
<option value="RON">RON (lei)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full bg-primary hover:bg-primary/90 text-white py-3 rounded-lg font-semibold transition-colors" data-translate="register.create_account">Create Account</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-sm">
|
||||
<span data-translate="register.have_account">Already have an account?</span>
|
||||
<a href="{{ url_for('auth.login') }}" class="text-primary hover:underline ml-1" data-translate="register.login">Login</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('register-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const data = Object.fromEntries(formData);
|
||||
|
||||
try {
|
||||
const response = await fetch('/auth/register', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
window.location.href = result.redirect;
|
||||
} else {
|
||||
showToast(result.message || 'Registration failed', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('An error occurred', 'error');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
100
app/templates/auth/setup_2fa.html
Normal file
100
app/templates/auth/setup_2fa.html
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Setup 2FA - FINA{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen flex items-center justify-center p-4 bg-background-light dark:bg-background-dark">
|
||||
<div class="max-w-md w-full">
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-8">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 bg-primary/10 rounded-full mb-4">
|
||||
<span class="material-symbols-outlined text-primary text-[32px]">lock</span>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-text-main dark:text-white mb-2" data-translate="twofa.setupTitle">Setup Two-Factor Authentication</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9]" data-translate="twofa.setupDesc">Scan the QR code with your authenticator app</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-2xl p-6 md:p-8 shadow-sm">
|
||||
<!-- Instructions -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-sm font-semibold text-text-main dark:text-white mb-3" data-translate="twofa.step1">Step 1: Scan QR Code</h3>
|
||||
<p class="text-sm text-text-muted dark:text-[#92adc9] mb-4" data-translate="twofa.step1Desc">Open your authenticator app (Google Authenticator, Authy, etc.) and scan this QR code:</p>
|
||||
|
||||
<!-- QR Code -->
|
||||
<div class="bg-white p-4 rounded-xl flex justify-center border border-border-light">
|
||||
<img src="data:image/png;base64,{{ qr_code }}" alt="2FA QR Code" class="max-w-full h-auto" style="max-width: 200px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Manual Entry -->
|
||||
<div class="mb-6">
|
||||
<details class="group">
|
||||
<summary class="cursor-pointer list-none flex items-center justify-between text-sm font-medium text-text-main dark:text-white mb-2">
|
||||
<span data-translate="twofa.manualEntry">Can't scan? Enter code manually</span>
|
||||
<span class="material-symbols-outlined text-[20px] group-open:rotate-180 transition-transform">expand_more</span>
|
||||
</summary>
|
||||
<div class="mt-3 bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg p-4">
|
||||
<p class="text-xs text-text-muted dark:text-[#92adc9] mb-2" data-translate="twofa.enterManually">Enter this code in your authenticator app:</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<code id="secret-code" class="flex-1 text-primary font-mono text-sm break-all">{{ secret }}</code>
|
||||
<button onclick="copySecret()" class="p-2 text-text-muted dark:text-[#92adc9] hover:text-primary transition-colors" title="Copy">
|
||||
<span class="material-symbols-outlined text-[20px]">content_copy</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- Verification -->
|
||||
<form method="POST" class="space-y-4">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-text-main dark:text-white mb-3" data-translate="twofa.step2">Step 2: Verify Code</h3>
|
||||
<p class="text-sm text-text-muted dark:text-[#92adc9] mb-3" data-translate="twofa.step2Desc">Enter the 6-digit code from your authenticator app:</p>
|
||||
<input type="text" name="code" maxlength="6" pattern="[0-9]{6}" required
|
||||
class="w-full bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white text-center text-2xl tracking-widest font-mono focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none transition-all"
|
||||
placeholder="000000"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full bg-primary hover:bg-primary/90 text-white py-3 rounded-lg font-semibold transition-colors flex items-center justify-center gap-2">
|
||||
<span class="material-symbols-outlined text-[20px]">verified_user</span>
|
||||
<span data-translate="twofa.enable">Enable 2FA</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-6 text-center">
|
||||
<a href="{{ url_for('main.settings') }}" class="text-text-muted dark:text-[#92adc9] hover:text-primary transition-colors text-sm" data-translate="actions.cancel">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info -->
|
||||
<div class="mt-6 bg-blue-50 dark:bg-blue-500/10 border border-blue-200 dark:border-blue-500/30 rounded-lg p-4">
|
||||
<div class="flex gap-3">
|
||||
<span class="material-symbols-outlined text-blue-600 dark:text-blue-400 text-[20px] flex-shrink-0">info</span>
|
||||
<p class="text-sm text-blue-700 dark:text-blue-300" data-translate="twofa.infoText">After enabling 2FA, you'll receive backup codes that you can use if you lose access to your authenticator app. Keep them in a safe place!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copySecret() {
|
||||
const secretCode = document.getElementById('secret-code').textContent;
|
||||
navigator.clipboard.writeText(secretCode).then(() => {
|
||||
// Show success notification
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'fixed top-4 right-4 z-50 px-4 py-3 rounded-lg shadow-lg flex items-center gap-3 bg-green-500 text-white animate-slideIn';
|
||||
notification.innerHTML = `
|
||||
<span class="material-symbols-outlined text-[20px]">check_circle</span>
|
||||
<span class="text-sm font-medium">Secret code copied!</span>
|
||||
`;
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.classList.add('animate-slideOut');
|
||||
setTimeout(() => document.body.removeChild(notification), 300);
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
199
app/templates/base.html
Normal file
199
app/templates/base.html
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="FINA - Track your expenses, manage your finances">
|
||||
<meta name="theme-color" content="#111a22">
|
||||
<title>{% block title %}FINA - Personal Finance Tracker{% endblock %}</title>
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='icons/favicon.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="{{ url_for('static', filename='icons/icon-96x96.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="{{ url_for('static', filename='icons/icon-192x192.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="{{ url_for('static', filename='icons/icon-512x512.png') }}">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='icons/apple-touch-icon.png') }}">
|
||||
|
||||
<!-- Preconnect for faster resource loading -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="preconnect" href="https://cdn.tailwindcss.com">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"primary": "#2b8cee",
|
||||
"background-light": "#f6f7f8",
|
||||
"background-dark": "#111a22",
|
||||
"card-dark": "#1a2632",
|
||||
"card-light": "#ffffff",
|
||||
"sidebar-light": "#ffffff",
|
||||
"border-light": "#e2e8f0",
|
||||
"text-main": "#0f172a",
|
||||
"text-muted": "#64748b",
|
||||
},
|
||||
fontFamily: {
|
||||
"display": ["Inter", "sans-serif"]
|
||||
},
|
||||
borderRadius: {
|
||||
"DEFAULT": "0.25rem",
|
||||
"lg": "0.5rem",
|
||||
"xl": "0.75rem",
|
||||
"2xl": "1rem",
|
||||
"full": "9999px"
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Dark theme scrollbar */
|
||||
.dark ::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
.dark ::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background: #324d67;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: #4b6a88;
|
||||
}
|
||||
|
||||
/* Light theme scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbarlight dark:bg-background-dark text-text-main dark:text-white font-display overflow-hidden transition-colors duration-200
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
|
||||
/* Fix icon picker text overflow */
|
||||
#icon-grid .material-symbols-outlined {
|
||||
font-size: 24px !important;
|
||||
max-width: 32px !important;
|
||||
max-height: 32px !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: clip !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.glass {
|
||||
background: rgba(26, 38, 50, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(35, 54, 72, 0.5);
|
||||
}
|
||||
|
||||
/* Optimize rendering */
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% block extra_css %}{% endblock %}
|
||||
|
||||
<!-- Prevent theme flashing by applying theme before page render -->
|
||||
<script>
|
||||
(function() {
|
||||
const theme = localStorage.getItem('theme') || 'dark';
|
||||
if (theme === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
// Sync user language preference from database to localStorage
|
||||
const dbLanguage = '{{ current_user.language }}';
|
||||
const storedLanguage = localStorage.getItem('language');
|
||||
|
||||
// Always use database language as source of truth
|
||||
if (dbLanguage && dbLanguage !== storedLanguage) {
|
||||
localStorage.setItem('language', dbLanguage);
|
||||
}
|
||||
{% endif %}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-background-light dark:bg-background-dark text-text-main dark:text-white font-display overflow-hidden">
|
||||
{% block body %}{% endblock %}
|
||||
|
||||
<!-- Global Search Modal -->
|
||||
<div id="global-search-modal" class="hidden fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-start justify-center pt-20 opacity-0 transition-opacity duration-200">
|
||||
<div class="w-full max-w-2xl mx-4 bg-card-light dark:bg-card-dark rounded-2xl shadow-2xl overflow-hidden">
|
||||
<!-- Search Input -->
|
||||
<div class="p-4 border-b border-border-light dark:border-[#233648]">
|
||||
<div class="relative">
|
||||
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-text-muted dark:text-[#92adc9]">search</span>
|
||||
<input
|
||||
type="text"
|
||||
id="global-search-input"
|
||||
placeholder="Search everything..."
|
||||
data-translate-placeholder="search.inputPlaceholder"
|
||||
class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg pl-10 pr-10 py-3 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary transition-all"
|
||||
/>
|
||||
<button id="global-search-close" class="absolute right-3 top-1/2 -translate-y-1/2 text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2 text-xs text-text-muted dark:text-[#92adc9] flex items-center gap-4">
|
||||
<span data-translate="search.pressEnter">Press Enter to search</span>
|
||||
<span>•</span>
|
||||
<span data-translate="search.pressEsc">ESC to close</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search Results -->
|
||||
<div id="global-search-results" class="max-h-[60vh] overflow-y-auto">
|
||||
<div class="p-8 text-center">
|
||||
<span class="material-symbols-outlined text-5xl text-text-muted dark:text-[#92adc9] opacity-50 mb-3">search</span>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-sm" data-translate="search.placeholder">Search for transactions, documents, categories, or features</p>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-xs mt-2" data-translate="search.hint">Press Ctrl+K to open search</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast Notification Container -->
|
||||
<div id="toast-container" class="fixed top-4 right-4 z-50 space-y-2"></div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/i18n.js') }}?v=2.0.3"></script>
|
||||
<script src="{{ url_for('static', filename='js/app.js') }}?v=2.0.3"></script>
|
||||
<script src="{{ url_for('static', filename='js/search.js') }}?v=2.0.3"></script>
|
||||
<script src="{{ url_for('static', filename='js/budget.js') }}?v=2.0.3"></script>
|
||||
<script src="{{ url_for('static', filename='js/notifications.js') }}?v=2.0.3"></script>
|
||||
<script src="{{ url_for('static', filename='js/pwa.js') }}?v=2.0.3"></script>
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
434
app/templates/dashboard.html
Normal file
434
app/templates/dashboard.html
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Dashboard - FINA{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<style>
|
||||
/* Custom scrollbar for pie chart legend */
|
||||
#pie-legend::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
#pie-legend::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
#pie-legend::-webkit-scrollbar-thumb {
|
||||
background: #92adc9;
|
||||
border-radius: 2px;
|
||||
}
|
||||
#pie-legend::-webkit-scrollbar-thumb:hover {
|
||||
background: #5f7a96;
|
||||
}
|
||||
.dark #pie-legend::-webkit-scrollbar-thumb {
|
||||
background: #233648;
|
||||
}
|
||||
.dark #pie-legend::-webkit-scrollbar-thumb:hover {
|
||||
background: #324d67;
|
||||
}
|
||||
|
||||
/* Smooth transform for drag and drop */
|
||||
.category-card {
|
||||
transition: transform 0.2s ease, opacity 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
/* Touch user select - prevent text selection during hold */
|
||||
.category-card {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex h-screen w-full">
|
||||
<!-- Side Navigation -->
|
||||
<aside id="sidebar" class="hidden lg:flex w-64 flex-col bg-sidebar-light dark:bg-background-dark border-r border-border-light dark:border-[#233648] transition-all duration-300 shadow-sm dark:shadow-none">
|
||||
<div class="p-6 flex flex-col h-full justify-between">
|
||||
<div class="flex flex-col gap-8">
|
||||
<!-- User Profile -->
|
||||
<div class="flex gap-3 items-center">
|
||||
<img src="{{ current_user.avatar | avatar_url }}" alt="{{ current_user.username }}" class="size-10 rounded-full border-2 border-primary/30 object-cover">
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-text-main dark:text-white text-base font-bold leading-none">{{ current_user.username }}</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-xs font-normal mt-1">
|
||||
{% if current_user.is_admin %}<span data-translate="user.admin">Admin</span>{% else %}<span data-translate="user.user">User</span>{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Links -->
|
||||
<nav class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-primary/10 text-primary border border-primary/10" href="{{ url_for('main.dashboard') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">dashboard</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.dashboard">Dashboard</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.transactions') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">receipt_long</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.transactions">Transactions</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.income') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">payments</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.income">Income</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.recurring') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">repeat</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.recurring">Recurring</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.import_page') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">file_upload</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.import">Import CSV</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.reports') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">pie_chart</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.reports">Reports</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.documents') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">folder_open</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.documents">Documents</span>
|
||||
</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="/admin">
|
||||
<span class="material-symbols-outlined text-[20px]">admin_panel_settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.admin">Admin</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Links -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<button id="theme-toggle" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]" id="theme-icon">light_mode</span>
|
||||
<span class="text-sm font-medium" id="theme-text" data-translate="dashboard.lightMode">Light Mode</span>
|
||||
</button>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.settings') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.settings">Settings</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('auth.logout') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">logout</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.logout">Log out</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative">
|
||||
<!-- Top Header -->
|
||||
<header class="h-16 flex items-center justify-between px-6 lg:px-8 border-b border-border-light dark:border-[#233648] bg-white/80 dark:bg-background-dark/95 backdrop-blur z-10 shrink-0 shadow-sm dark:shadow-none">
|
||||
<div class="flex items-center gap-4">
|
||||
<button id="menu-toggle" class="lg:hidden text-text-main dark:text-white">
|
||||
<span class="material-symbols-outlined">menu</span>
|
||||
</button>
|
||||
<h2 class="text-text-main dark:text-white text-lg font-bold" data-translate="nav.dashboard">Dashboard</h2>
|
||||
</div>
|
||||
<div class="flex items-center gap-6">
|
||||
<!-- Global Search Button -->
|
||||
<button id="global-search-btn" class="hidden md:flex items-center bg-slate-50 dark:bg-card-dark rounded-lg h-10 px-3 border border-border-light dark:border-[#233648] hover:border-primary transition-colors w-64 text-left">
|
||||
<span class="material-symbols-outlined text-text-muted dark:text-[#92adc9] text-[20px]">search</span>
|
||||
<span class="text-sm text-text-muted dark:text-[#92adc9] ml-2 flex-1" data-translate="search.inputPlaceholder">Search everything...</span>
|
||||
<kbd class="hidden lg:inline-block px-2 py-0.5 text-xs font-semibold text-text-muted dark:text-[#92adc9] bg-slate-100 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded">⌘K</kbd>
|
||||
</button>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center gap-3">
|
||||
<button id="add-expense-btn" class="bg-primary hover:bg-primary/90 text-white h-9 px-4 rounded-lg text-sm font-semibold shadow-md shadow-primary/20 transition-all flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-[18px]">add</span>
|
||||
<span class="hidden sm:inline" data-translate="actions.add_expense">Add Expense</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Scrollable Content -->
|
||||
<div class="flex-1 overflow-y-auto p-6 lg:p-8 scroll-smooth bg-[#f8fafc] dark:bg-background-dark">
|
||||
<div class="max-w-7xl mx-auto flex flex-col gap-8 pb-10">
|
||||
<!-- KPI Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-6">
|
||||
<!-- Total Income -->
|
||||
<div class="p-6 rounded-xl bg-white dark:bg-card-dark border border-green-500/20 dark:border-green-500/30 flex flex-col justify-between relative overflow-hidden group shadow-sm dark:shadow-none">
|
||||
<div class="absolute top-0 right-0 p-4 opacity-5 dark:opacity-10 group-hover:opacity-10 dark:group-hover:opacity-20 transition-opacity">
|
||||
<span class="material-symbols-outlined text-6xl text-green-500">trending_up</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-sm font-medium" data-translate="dashboard.total_income">Total Income</p>
|
||||
<h3 id="total-income" class="text-green-600 dark:text-green-400 text-3xl font-bold mt-2 tracking-tight">$0.00</h3>
|
||||
</div>
|
||||
<p class="text-text-muted dark:text-[#5f7a96] text-xs mt-4" data-translate="dashboard.this_month">this month</p>
|
||||
</div>
|
||||
|
||||
<!-- Total Spent -->
|
||||
<div class="p-6 rounded-xl bg-white dark:bg-card-dark border border-red-500/20 dark:border-red-500/30 flex flex-col justify-between relative overflow-hidden group shadow-sm dark:shadow-none">
|
||||
<div class="absolute top-0 right-0 p-4 opacity-5 dark:opacity-10 group-hover:opacity-10 dark:group-hover:opacity-20 transition-opacity">
|
||||
<span class="material-symbols-outlined text-6xl text-red-500">trending_down</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-sm font-medium" data-translate="dashboard.total_spent">Total Spent</p>
|
||||
<h3 id="total-spent" class="text-red-600 dark:text-red-400 text-3xl font-bold mt-2 tracking-tight">$0.00</h3>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mt-4">
|
||||
<span id="percent-change" class="bg-green-500/10 text-green-600 dark:text-green-400 text-xs font-semibold px-2 py-1 rounded-full flex items-center gap-1">
|
||||
<span class="material-symbols-outlined text-[14px]">trending_up</span>
|
||||
0%
|
||||
</span>
|
||||
<span class="text-text-muted dark:text-[#5f7a96] text-xs" data-translate="dashboard.vs_last_month">vs last month</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profit/Loss -->
|
||||
<div class="p-6 rounded-xl bg-white dark:bg-card-dark border border-border-light dark:border-[#233648] flex flex-col justify-between relative overflow-hidden group shadow-sm dark:shadow-none">
|
||||
<div class="absolute top-0 right-0 p-4 opacity-5 dark:opacity-10 group-hover:opacity-10 dark:group-hover:opacity-20 transition-opacity">
|
||||
<span class="material-symbols-outlined text-6xl text-primary">account_balance</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-sm font-medium" data-translate="dashboard.profit_loss">Profit/Loss</p>
|
||||
<h3 id="profit-loss" class="text-text-main dark:text-white text-3xl font-bold mt-2 tracking-tight">$0.00</h3>
|
||||
</div>
|
||||
<p class="text-text-muted dark:text-[#5f7a96] text-xs mt-4" data-translate="dashboard.this_month">this month</p>
|
||||
</div>
|
||||
|
||||
<!-- Total Transactions -->
|
||||
<div class="p-6 rounded-xl bg-white dark:bg-card-dark border border-border-light dark:border-[#233648] flex flex-col justify-between shadow-sm dark:shadow-none">
|
||||
<div>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-sm font-medium" data-translate="dashboard.total_transactions">Total Transactions</p>
|
||||
<h3 id="total-transactions" class="text-text-main dark:text-white text-3xl font-bold mt-2 tracking-tight">0</h3>
|
||||
</div>
|
||||
<p class="text-text-muted dark:text-[#5f7a96] text-xs mt-4" data-translate="dashboard.this_month">this month</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Row -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 lg:gap-6">
|
||||
<!-- Spending by Category - Smaller, Compact -->
|
||||
<div class="p-5 rounded-xl bg-white dark:bg-card-dark border border-border-light dark:border-[#233648] shadow-sm dark:shadow-none flex flex-col">
|
||||
<h3 class="text-text-main dark:text-white text-base font-bold mb-1" data-translate="dashboard.spending_by_category">Spending by Category</h3>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-xs mb-4" data-translate="dashboard.categoryBreakdownDesc">Breakdown by category</p>
|
||||
<div class="flex items-center justify-center relative mb-4">
|
||||
<!-- CSS Conic Gradient Pie Chart - Smaller Size -->
|
||||
<div id="pie-chart-wrapper" class="relative flex items-center justify-center">
|
||||
<div id="pie-chart" class="size-40 rounded-full relative transition-all duration-500" style="background: conic-gradient(#233648 0% 100%);">
|
||||
<!-- Inner hole for donut effect -->
|
||||
<div class="absolute inset-3 bg-white dark:bg-card-dark rounded-full flex flex-col items-center justify-center z-10 border border-border-light dark:border-[#233648]">
|
||||
<span class="text-text-muted dark:text-[#92adc9] text-[10px] font-medium" data-translate="dashboard.totalThisYear">Total This Year</span>
|
||||
<span id="pie-total" class="text-text-main dark:text-white text-base font-bold">0 lei</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Legend - Scrollable for 12-14 categories -->
|
||||
<div id="pie-legend" class="grid grid-cols-1 gap-y-1.5 max-h-[180px] overflow-y-auto pr-2 scrollbar-thin scrollbar-thumb-border-light dark:scrollbar-thumb-[#233648] scrollbar-track-transparent">
|
||||
<!-- Legend items will be generated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Monthly Trend - Larger Space for 12 Months -->
|
||||
<div class="lg:col-span-2 p-6 rounded-xl bg-white dark:bg-card-dark border border-border-light dark:border-[#233648] shadow-sm dark:shadow-none">
|
||||
<h3 class="text-text-main dark:text-white text-lg font-bold mb-4" data-translate="dashboard.monthly_trend">Monthly Trend</h3>
|
||||
<canvas id="monthly-chart" class="w-full" style="max-height: 320px;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expense Categories -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-text-main dark:text-white text-lg font-bold" data-translate="dashboard.expenseCategories">Expense Categories</h3>
|
||||
<span class="material-symbols-outlined text-text-muted dark:text-[#92adc9] text-[18px]" title="Drag to reorder">drag_indicator</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button id="manage-categories-btn" class="text-primary hover:text-primary/80 text-sm font-medium flex items-center gap-1">
|
||||
<span class="material-symbols-outlined text-[18px]">tune</span>
|
||||
<span data-translate="dashboard.manageCategories">Manage</span>
|
||||
</button>
|
||||
<a href="{{ url_for('main.transactions') }}" class="text-primary text-sm font-medium hover:text-primary/80" data-translate="dashboard.view_all">View All</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="category-cards" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Category cards will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Transactions -->
|
||||
<div class="p-6 rounded-xl bg-white dark:bg-card-dark border border-border-light dark:border-[#233648] shadow-sm dark:shadow-none">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-text-main dark:text-white text-lg font-bold" data-translate="dashboard.recent_transactions">Recent Transactions</h3>
|
||||
<a href="{{ url_for('main.transactions') }}" class="text-primary text-sm hover:underline" data-translate="dashboard.view_all">View All</a>
|
||||
</div>
|
||||
<div id="recent-transactions" class="space-y-3">
|
||||
<!-- Transactions will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Add Expense Modal -->
|
||||
<div id="expense-modal" class="hidden fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||
<div class="bg-white dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-2xl max-w-md w-full max-h-[90vh] overflow-y-auto shadow-xl">
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-text-main dark:text-white text-xl font-bold" data-translate="modal.add_expense">Add Expense</h3>
|
||||
<button id="close-modal" class="text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="expense-form" class="space-y-4">
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.amount">Amount</label>
|
||||
<input type="number" step="0.01" name="amount" required class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.description">Description</label>
|
||||
<input type="text" id="expense-description" name="description" required class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.category">Category</label>
|
||||
<select name="category_id" required class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
<option value="" data-translate="dashboard.selectCategory">Select category...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.date">Date</label>
|
||||
<input type="date" name="date" required class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.tags">Tags (comma separated)</label>
|
||||
<input type="text" id="expense-tags" name="tags" placeholder="coffee, dining, work..." class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.receipt">Receipt (optional)</label>
|
||||
<input type="file" name="receipt" accept="image/*,.pdf" class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-primary file:text-white hover:file:bg-primary/90">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-full bg-primary hover:bg-primary/90 text-white py-3 rounded-lg font-semibold transition-colors shadow-md" data-translate="actions.save">Save Expense</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category Management Modal -->
|
||||
<div id="category-modal" class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
|
||||
<div class="bg-white dark:bg-card-dark rounded-2xl max-w-4xl w-full max-h-[90vh] overflow-hidden shadow-2xl">
|
||||
<div class="p-6 border-b border-border-light dark:border-[#233648] flex justify-between items-center">
|
||||
<h3 class="text-text-main dark:text-white text-xl font-bold" data-translate="categories.manageTitle">Manage Categories</h3>
|
||||
<button id="close-category-modal" class="text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6 overflow-y-auto max-h-[calc(90vh-140px)]">
|
||||
<!-- Add New Category Form -->
|
||||
<div class="mb-6 p-4 bg-slate-50 dark:bg-[#111a22] rounded-lg border border-border-light dark:border-[#233648]">
|
||||
<h4 class="text-text-main dark:text-white font-semibold mb-4" data-translate="categories.addNew">Add New Category</h4>
|
||||
<form id="add-category-form" class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.name">Name</label>
|
||||
<input type="text" name="name" required class="w-full bg-white dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.color">Color</label>
|
||||
<input type="color" name="color" value="#2b8cee" class="w-full h-10 bg-white dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-lg px-2 cursor-pointer" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.icon">Icon</label>
|
||||
<input type="hidden" name="icon" value="category" />
|
||||
<button type="button" onclick="openIconPicker('add-form')"
|
||||
class="w-full h-10 bg-white dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-lg px-4 flex items-center justify-between hover:border-primary/50 transition-colors">
|
||||
<div class="flex items-center gap-2">
|
||||
<span id="add-form-icon-preview" class="material-symbols-outlined text-primary">category</span>
|
||||
<span id="add-form-icon-name" class="text-text-main dark:text-white text-sm">category</span>
|
||||
</div>
|
||||
<span class="material-symbols-outlined text-text-muted dark:text-[#92adc9] text-[16px]">expand_more</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button type="submit" class="w-full bg-primary hover:bg-primary/90 text-white py-2 rounded-lg font-semibold transition-colors flex items-center justify-center gap-2">
|
||||
<span class="material-symbols-outlined text-[18px]">add</span>
|
||||
<span data-translate="categories.add">Add</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Category List with Drag & Drop -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h4 class="text-text-main dark:text-white font-semibold" data-translate="categories.yourCategories">Your Categories</h4>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-sm" data-translate="categories.dragToReorder">Drag to reorder</p>
|
||||
</div>
|
||||
<div id="categories-list" class="space-y-2">
|
||||
<!-- Categories will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Icon Picker Modal -->
|
||||
<div id="icon-picker-modal" class="hidden fixed inset-0 bg-black/60 z-[60] flex items-center justify-center p-4">
|
||||
<div class="bg-white dark:bg-card-dark rounded-2xl max-w-3xl w-full max-h-[85vh] overflow-hidden shadow-2xl border border-border-light dark:border-[#233648] relative">
|
||||
<div class="p-4 border-b border-border-light dark:border-[#233648] flex justify-between items-center sticky top-0 bg-white dark:bg-card-dark z-10">
|
||||
<div class="flex-1">
|
||||
<h3 class="text-text-main dark:text-white text-lg font-bold mb-2" data-translate="categories.selectIcon">Select Icon</h3>
|
||||
<input type="text" id="icon-search" placeholder="Search icons..."
|
||||
class="w-full bg-slate-50 dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-3 py-2 text-sm text-text-main dark:text-white focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
data-translate-placeholder="categories.searchIcons" />
|
||||
</div>
|
||||
<button onclick="closeIconPicker()" class="ml-4 text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-4 overflow-y-auto max-h-[calc(85vh-140px)] bg-white dark:bg-card-dark">
|
||||
<div id="icon-grid" class="grid grid-cols-5 sm:grid-cols-7 md:grid-cols-9 gap-2 relative z-10">
|
||||
<!-- Icons will be populated by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category Expenses Modal -->
|
||||
<div id="category-expenses-modal" class="hidden fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4" onclick="if (event.target === this) closeCategoryExpensesModal()">
|
||||
<div class="bg-white dark:bg-card-dark rounded-2xl max-w-4xl w-full max-h-[90vh] overflow-hidden shadow-2xl border border-border-light dark:border-[#233648]">
|
||||
<div class="p-6 border-b border-border-light dark:border-[#233648] flex justify-between items-center sticky top-0 bg-white dark:bg-card-dark z-10">
|
||||
<div class="flex items-center gap-3">
|
||||
<div id="modal-category-icon-container" class="w-10 h-10 rounded-lg flex items-center justify-center">
|
||||
<span id="modal-category-icon" class="material-symbols-outlined text-white text-[20px]"></span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 id="modal-category-name" class="text-text-main dark:text-white text-lg font-bold"></h3>
|
||||
<p id="modal-category-count" class="text-text-muted dark:text-[#92adc9] text-sm"></p>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="closeCategoryExpensesModal()" class="text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6 overflow-y-auto max-h-[calc(90vh-120px)]">
|
||||
<div id="modal-expenses-list" class="space-y-3">
|
||||
<!-- Expenses will be loaded here -->
|
||||
</div>
|
||||
<div id="modal-expenses-empty" class="hidden text-center py-12">
|
||||
<span class="material-symbols-outlined text-[48px] text-text-muted dark:text-[#92adc9] mb-3">inbox</span>
|
||||
<p class="text-text-muted dark:text-[#92adc9]" data-translate="categories.noExpenses">No expenses in this category</p>
|
||||
</div>
|
||||
<div id="modal-expenses-loading" class="hidden text-center py-12">
|
||||
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/dashboard.js') }}?v=2.0.3"></script>
|
||||
{% endblock %}
|
||||
149
app/templates/documents.html
Normal file
149
app/templates/documents.html
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Documents - FINA{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex h-screen w-full">
|
||||
<!-- Sidebar -->
|
||||
<aside id="sidebar" class="hidden lg:flex w-64 flex-col bg-sidebar-light dark:bg-background-dark border-r border-border-light dark:border-[#233648]">
|
||||
<div class="p-6 flex flex-col h-full justify-between">
|
||||
<div class="flex flex-col gap-8">
|
||||
<div class="flex gap-3 items-center">
|
||||
<img src="{{ current_user.avatar | avatar_url }}" alt="{{ current_user.username }}" class="size-10 rounded-full border-2 border-primary/30 object-cover">
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-text-main dark:text-white text-base font-bold leading-none">{{ current_user.username }}</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-xs font-normal mt-1">
|
||||
{% if current_user.is_admin %}<span data-translate="user.admin">Admin</span>{% else %}<span data-translate="user.user">User</span>{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.dashboard') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">dashboard</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.dashboard">Dashboard</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.transactions') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">receipt_long</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.transactions">Transactions</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.income') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">payments</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.income">Income</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.recurring') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">repeat</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.recurring">Recurring</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.import_page') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">file_upload</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.import">Import CSV</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.reports') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">pie_chart</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.reports">Reports</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-primary/20 text-primary border border-primary/10" href="{{ url_for('main.documents') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">folder_open</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.documents">Documents</span>
|
||||
</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.admin') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">admin_panel_settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.admin">Admin</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<button id="theme-toggle" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]" id="theme-icon">light_mode</span>
|
||||
<span class="text-sm font-medium" id="theme-text" data-translate="dashboard.lightMode">Light Mode</span>
|
||||
</button>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.settings') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.settings">Settings</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('auth.logout') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">logout</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.logout">Log out</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative bg-background-light dark:bg-background-dark">
|
||||
<header class="h-16 flex items-center justify-between px-6 lg:px-8 border-b border-border-light dark:border-[#233648] bg-card-light/95 dark:bg-background-dark/80 backdrop-blur z-10 shrink-0">
|
||||
<div class="flex items-center gap-4">
|
||||
<button id="menu-toggle" class="lg:hidden text-text-main dark:text-white">
|
||||
<span class="material-symbols-outlined">menu</span>
|
||||
</button>
|
||||
<h2 class="text-text-main dark:text-white text-lg font-bold" data-translate="documents.title">Documents</h2>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-6 lg:p-8 scroll-smooth">
|
||||
<div class="max-w-7xl mx-auto flex flex-col gap-8 pb-10">
|
||||
<!-- Upload Section -->
|
||||
<div class="flex flex-col gap-4">
|
||||
<h3 class="text-base font-semibold text-text-main dark:text-white" data-translate="documents.uploadTitle">Upload Documents</h3>
|
||||
<div id="upload-area" class="bg-card-light dark:bg-card-dark border-2 border-dashed border-border-light dark:border-[#233648] rounded-xl p-10 flex flex-col items-center justify-center text-center hover:border-primary/50 hover:bg-slate-50 dark:hover:bg-white/[0.02] transition-all cursor-pointer group relative overflow-hidden">
|
||||
<input id="file-input" type="file" class="absolute inset-0 opacity-0 cursor-pointer z-10" accept=".pdf,.csv,.xlsx,.xls,.png,.jpg,.jpeg" multiple />
|
||||
<div class="bg-primary/10 p-4 rounded-full text-primary mb-4 group-hover:scale-110 transition-transform duration-300">
|
||||
<span class="material-symbols-outlined text-[32px]">cloud_upload</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-text-main dark:text-white mb-1" data-translate="documents.dragDrop">Drag & drop files here or click to browse</h3>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-sm max-w-sm leading-relaxed">
|
||||
<span data-translate="documents.uploadDesc">Upload bank statements, invoices, or receipts.</span><br/>
|
||||
<span class="text-xs text-text-muted/70 dark:text-[#92adc9]/70" data-translate="documents.supportedFormats">Supported formats: CSV, PDF, XLS, XLSX, PNG, JPG (Max 10MB)</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Documents List -->
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
|
||||
<h3 class="text-base font-semibold text-text-main dark:text-white" data-translate="documents.yourFiles">Your Files</h3>
|
||||
<div class="flex flex-col sm:flex-row gap-3 w-full md:w-auto">
|
||||
<div class="relative flex-1 min-w-[240px]">
|
||||
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-text-muted dark:text-[#92adc9] text-[20px]">search</span>
|
||||
<input id="search-input" type="text" class="w-full bg-card-light dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-lg py-2 pl-10 pr-4 text-sm text-text-main dark:text-white focus:ring-1 focus:ring-primary focus:border-primary outline-none transition-all placeholder:text-text-muted/50 dark:placeholder:text-[#92adc9]/50" placeholder="Search by name..." data-translate="documents.searchPlaceholder" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-xl overflow-hidden shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm text-left">
|
||||
<thead class="text-xs text-text-muted dark:text-[#92adc9] uppercase bg-slate-50 dark:bg-white/5 border-b border-border-light dark:border-[#233648]">
|
||||
<tr>
|
||||
<th class="px-6 py-4 font-medium" data-translate="documents.tableDocName">Document Name</th>
|
||||
<th class="px-6 py-4 font-medium" data-translate="documents.tableUploadDate">Upload Date</th>
|
||||
<th class="px-6 py-4 font-medium" data-translate="documents.tableType">Type</th>
|
||||
<th class="px-6 py-4 font-medium" data-translate="documents.tableStatus">Status</th>
|
||||
<th class="px-6 py-4 font-medium text-right" data-translate="documents.tableActions">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="documents-list" class="divide-y divide-border-light dark:divide-[#233648]">
|
||||
<!-- Documents will be loaded here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="bg-slate-50 dark:bg-white/5 border-t border-border-light dark:border-[#233648] p-4 flex items-center justify-between">
|
||||
<span class="text-sm text-text-muted dark:text-[#92adc9]">
|
||||
<span data-translate="documents.showing">Showing</span> <span id="page-start" class="text-text-main dark:text-white font-medium">1</span>-<span id="page-end" class="text-text-main dark:text-white font-medium">5</span> <span data-translate="documents.of">of</span> <span id="total-count" class="text-text-main dark:text-white font-medium">0</span> <span data-translate="documents.documents">documents</span>
|
||||
</span>
|
||||
<div id="pagination" class="flex gap-2">
|
||||
<!-- Pagination buttons will be added here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/documents.js') }}"></script>
|
||||
{% endblock %}
|
||||
114
app/templates/import.html
Normal file
114
app/templates/import.html
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Import CSV - FINA{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex h-screen w-full">
|
||||
<!-- Sidebar -->
|
||||
<aside id="sidebar" class="hidden lg:flex w-64 flex-col bg-sidebar-light dark:bg-background-dark border-r border-border-light dark:border-[#233648]">
|
||||
<div class="p-6 flex flex-col h-full justify-between">
|
||||
<div class="flex flex-col gap-8">
|
||||
<div class="flex gap-3 items-center">
|
||||
<img src="{{ current_user.avatar | avatar_url }}" alt="{{ current_user.username }}" class="size-10 rounded-full border-2 border-primary/30 object-cover">
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-text-main dark:text-white text-base font-bold leading-none">{{ current_user.username }}</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-xs font-normal mt-1">
|
||||
{% if current_user.is_admin %}<span data-translate="user.admin">Admin</span>{% else %}<span data-translate="user.user">User</span>{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.dashboard') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">dashboard</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.dashboard">Dashboard</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.transactions') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">receipt_long</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.transactions">Transactions</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.income') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">payments</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.income">Income</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.recurring') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">repeat</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.recurring">Recurring</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-primary/20 text-primary border border-primary/10" href="{{ url_for('main.import_page') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">file_upload</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.import">Import CSV</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.reports') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">pie_chart</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.reports">Reports</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.documents') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">folder_open</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.documents">Documents</span>
|
||||
</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.admin') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">admin_panel_settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.admin">Admin</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<button id="theme-toggle" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors">
|
||||
<span id="theme-icon" class="material-symbols-outlined text-[20px]">dark_mode</span>
|
||||
<span id="theme-text" class="text-sm font-medium" data-translate="dashboard.darkMode">Dark Mode</span>
|
||||
</button>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.settings') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.settings">Settings</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-red-500 dark:text-red-400 hover:bg-red-500/10 transition-colors" href="{{ url_for('auth.logout') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">logout</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.logout">Log out</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<!-- Mobile Header -->
|
||||
<div class="lg:hidden bg-white dark:bg-[#0f1921] border-b border-border-light dark:border-[#233648] p-4 flex items-center justify-between">
|
||||
<button id="menu-toggle" class="text-text-main dark:text-white">
|
||||
<span class="material-symbols-outlined">menu</span>
|
||||
</button>
|
||||
<h1 class="text-lg font-bold text-text-main dark:text-white" data-translate="nav.import">Import CSV</h1>
|
||||
<div class="w-6"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto bg-background-light dark:bg-background-dark pb-20">
|
||||
<!-- Header -->
|
||||
<div class="bg-white dark:bg-[#0f1921] border-b border-border-light dark:border-[#233648] p-6">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-2xl font-bold text-text-main dark:text-white mb-2">
|
||||
<span class="material-symbols-outlined align-middle mr-2">file_upload</span>
|
||||
<span id="importTitle">Import CSV</span>
|
||||
</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9]" id="importSubtitle">
|
||||
Import your bank statements or expense CSV files
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="max-w-4xl mx-auto p-6">
|
||||
<div id="importContainer">
|
||||
<!-- Import UI will be rendered here by import.js -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="{{ url_for('static', filename='js/import.js') }}"></script>
|
||||
{% endblock %}
|
||||
320
app/templates/income.html
Normal file
320
app/templates/income.html
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Income - FINA{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex h-screen w-full">
|
||||
<!-- Side Navigation -->
|
||||
<aside id="sidebar" class="hidden lg:flex w-64 flex-col bg-sidebar-light dark:bg-background-dark border-r border-border-light dark:border-[#233648] transition-all duration-300 shadow-sm dark:shadow-none">
|
||||
<div class="p-6 flex flex-col h-full justify-between">
|
||||
<div class="flex flex-col gap-8">
|
||||
<!-- User Profile -->
|
||||
<div class="flex gap-3 items-center">
|
||||
<img src="{{ current_user.avatar | avatar_url }}" alt="{{ current_user.username }}" class="size-10 rounded-full border-2 border-primary/30 object-cover">
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-text-main dark:text-white text-base font-bold leading-none">{{ current_user.username }}</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-xs font-normal mt-1">
|
||||
{% if current_user.is_admin %}<span data-translate="user.admin">Admin</span>{% else %}<span data-translate="user.user">User</span>{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Links -->
|
||||
<nav class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.dashboard') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">dashboard</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.dashboard">Dashboard</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.transactions') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">receipt_long</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.transactions">Transactions</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-primary/10 text-primary border border-primary/10" href="{{ url_for('main.income') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">payments</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.income">Income</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.recurring') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">repeat</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.recurring">Recurring</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.import_page') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">file_upload</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.import">Import CSV</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.reports') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">pie_chart</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.reports">Reports</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.documents') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">folder_open</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.documents">Documents</span>
|
||||
</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="/admin">
|
||||
<span class="material-symbols-outlined text-[20px]">admin_panel_settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.admin">Admin</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Links -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.settings') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.settings">Settings</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-red-500 dark:hover:text-red-400 transition-colors" href="{{ url_for('auth.logout') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">logout</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.logout">Logout</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<div class="flex-1 flex flex-col min-h-screen overflow-hidden">
|
||||
<!-- Top Bar (Mobile) -->
|
||||
<header class="lg:hidden bg-white dark:bg-card-dark border-b border-border-light dark:border-[#233648] p-4 flex items-center justify-between">
|
||||
<button id="mobile-menu-toggle" class="text-text-main dark:text-white p-2 hover:bg-slate-100 dark:hover:bg-[#233648] rounded-lg transition-colors">
|
||||
<span class="material-symbols-outlined text-[24px]">menu</span>
|
||||
</button>
|
||||
<h1 class="text-lg font-bold text-text-main dark:text-white" data-translate="income.title">Income</h1>
|
||||
<div class="w-10"></div>
|
||||
</header>
|
||||
|
||||
<!-- Content -->
|
||||
<main class="flex-1 overflow-y-auto bg-background-light dark:bg-background-dark p-4 md:p-6 lg:p-8">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-6 gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl md:text-3xl font-bold text-text-main dark:text-white mb-2" data-translate="income.title">Income</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9]" data-translate="income.subtitle">Track your income sources</p>
|
||||
</div>
|
||||
<button onclick="openIncomeModal()" class="inline-flex items-center gap-2 bg-primary hover:bg-primary/90 text-white px-6 py-3 rounded-xl font-medium transition-all hover:shadow-lg">
|
||||
<span class="material-symbols-outlined text-[20px]">add</span>
|
||||
<span data-translate="income.addNew">Add Income</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Income Table -->
|
||||
<div class="bg-white dark:bg-card-dark rounded-2xl border border-border-light dark:border-[#233648] overflow-hidden shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-slate-50 dark:bg-[#111a22] border-b border-border-light dark:border-[#233648]">
|
||||
<tr>
|
||||
<th class="px-6 py-4 text-left text-xs font-medium text-text-muted dark:text-[#92adc9] uppercase tracking-wider" data-translate="income.tableDescription">Description</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-medium text-text-muted dark:text-[#92adc9] uppercase tracking-wider" data-translate="income.tableDate">Date</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-medium text-text-muted dark:text-[#92adc9] uppercase tracking-wider" data-translate="income.tableSource">Source</th>
|
||||
<th class="px-6 py-4 text-right text-xs font-medium text-text-muted dark:text-[#92adc9] uppercase tracking-wider" data-translate="income.tableAmount">Amount</th>
|
||||
<th class="px-6 py-4 text-right text-xs font-medium text-text-muted dark:text-[#92adc9] uppercase tracking-wider" data-translate="income.tableActions">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="income-table-body">
|
||||
<!-- Income entries will be populated by JavaScript -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Menu Overlay -->
|
||||
<div id="mobile-menu" class="lg:hidden hidden fixed inset-0 bg-black/60 backdrop-blur-sm z-50">
|
||||
<aside class="w-64 h-full bg-sidebar-light dark:bg-card-dark border-r border-border-light dark:border-[#233648] overflow-y-auto">
|
||||
<div class="p-6 flex flex-col h-full justify-between">
|
||||
<div class="flex flex-col gap-8">
|
||||
<!-- Close Button -->
|
||||
<button id="mobile-menu-close" class="self-end text-text-main dark:text-white p-2 hover:bg-slate-100 dark:hover:bg-[#233648] rounded-lg transition-colors">
|
||||
<span class="material-symbols-outlined text-[24px]">close</span>
|
||||
</button>
|
||||
|
||||
<!-- User Profile -->
|
||||
<div class="flex gap-3 items-center">
|
||||
<img src="{{ current_user.avatar | avatar_url }}" alt="{{ current_user.username }}" class="size-10 rounded-full border-2 border-primary/30 object-cover">
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-text-main dark:text-white text-base font-bold leading-none">{{ current_user.username }}</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-xs font-normal mt-1">
|
||||
{% if current_user.is_admin %}<span data-translate="user.admin">Admin</span>{% else %}<span data-translate="user.user">User</span>{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Links -->
|
||||
<nav class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.dashboard') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">dashboard</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.dashboard">Dashboard</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.transactions') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">receipt_long</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.transactions">Transactions</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-primary/10 text-primary border border-primary/10" href="{{ url_for('main.income') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">payments</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.income">Income</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.recurring') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">repeat</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.recurring">Recurring</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.import_page') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">file_upload</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.import">Import CSV</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.reports') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">pie_chart</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.reports">Reports</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.documents') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">folder_open</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.documents">Documents</span>
|
||||
</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="/admin">
|
||||
<span class="material-symbols-outlined text-[20px]">admin_panel_settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.admin">Admin</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Links -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.settings') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.settings">Settings</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-red-500 dark:hover:text-red-400 transition-colors" href="{{ url_for('auth.logout') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">logout</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.logout">Logout</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<!-- Income Modal -->
|
||||
<div id="income-modal" class="hidden fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4" onclick="if (event.target === this) closeIncomeModal()">
|
||||
<div class="bg-white dark:bg-card-dark rounded-2xl max-w-lg w-full border border-border-light dark:border-[#233648] shadow-2xl">
|
||||
<div class="p-6 border-b border-border-light dark:border-[#233648]">
|
||||
<h3 id="income-modal-title" class="text-xl font-bold text-text-main dark:text-white" data-translate="income.add">Add Income</h3>
|
||||
</div>
|
||||
|
||||
<form id="income-form" class="p-6">
|
||||
<div class="space-y-4">
|
||||
<!-- Amount -->
|
||||
<div>
|
||||
<label for="income-amount" class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="form.amount">Amount</label>
|
||||
<input type="number" id="income-amount" step="0.01" min="0" required
|
||||
class="w-full bg-slate-50 dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white focus:outline-none focus:ring-2 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<!-- Source -->
|
||||
<div>
|
||||
<label for="income-source" class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="income.source">Source</label>
|
||||
<select id="income-source" required class="income-source-select w-full bg-slate-50 dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white focus:outline-none focus:ring-2 focus:ring-primary">
|
||||
<option value="">Select source...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div>
|
||||
<label for="income-description" class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="form.description">Description</label>
|
||||
<input type="text" id="income-description" required
|
||||
class="w-full bg-slate-50 dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white focus:outline-none focus:ring-2 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<!-- Date -->
|
||||
<div>
|
||||
<label for="income-date" class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="form.date">Date</label>
|
||||
<input type="date" id="income-date" required
|
||||
class="w-full bg-slate-50 dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white focus:outline-none focus:ring-2 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<!-- Frequency (Recurring) -->
|
||||
<div>
|
||||
<label for="income-frequency" class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="income.frequency">Payment Frequency</label>
|
||||
<select id="income-frequency" class="w-full bg-slate-50 dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white focus:outline-none focus:ring-2 focus:ring-primary">
|
||||
<option value="once" data-translate="income.once">One-time</option>
|
||||
<option value="weekly" data-translate="income.weekly">Weekly</option>
|
||||
<option value="biweekly" data-translate="income.biweekly">Every 2 Weeks</option>
|
||||
<option value="every4weeks" data-translate="income.every4weeks">Every 4 Weeks</option>
|
||||
<option value="monthly" data-translate="income.monthly">Monthly</option>
|
||||
<option value="custom" data-translate="income.custom">Custom (Freelance)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Custom Frequency (shown when custom is selected) -->
|
||||
<div id="custom-frequency-container" class="hidden">
|
||||
<label for="income-custom-days" class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="income.customDays">Custom Days Interval</label>
|
||||
<input type="number" id="income-custom-days" min="1" placeholder="Number of days"
|
||||
class="w-full bg-slate-50 dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white focus:outline-none focus:ring-2 focus:ring-primary">
|
||||
<p class="text-xs text-text-muted dark:text-[#92adc9] mt-1" data-translate="income.customHelp">Enter the number of days between payments</p>
|
||||
</div>
|
||||
|
||||
<!-- Auto-create recurring income -->
|
||||
<div id="auto-create-container" class="bg-slate-50 dark:bg-[#111a22] rounded-lg p-4 border border-border-light dark:border-[#233648]">
|
||||
<label class="flex items-start gap-3 cursor-pointer">
|
||||
<input type="checkbox" id="income-auto-create" class="mt-1 w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary focus:ring-offset-0">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm font-medium text-text-main dark:text-white" data-translate="income.autoCreate">Automatically create income entries</span>
|
||||
<p class="text-xs text-text-muted dark:text-[#92adc9] mt-1" data-translate="income.autoCreateHelp">When enabled, income entries will be created automatically based on the frequency. You can edit or cancel at any time.</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Tags -->
|
||||
<div>
|
||||
<label for="income-tags" class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="form.tags">Tags (comma separated)</label>
|
||||
<input type="text" id="income-tags"
|
||||
class="w-full bg-slate-50 dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-3 text-text-main dark:text-white focus:outline-none focus:ring-2 focus:ring-primary">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 mt-6">
|
||||
<button type="submit" class="flex-1 bg-primary hover:bg-primary/90 text-white px-6 py-3 rounded-xl font-medium transition-all">
|
||||
<span data-translate="income.save">Save Income</span>
|
||||
</button>
|
||||
<button type="button" onclick="closeIncomeModal()" class="flex-1 bg-slate-100 dark:bg-[#111a22] hover:bg-slate-200 dark:hover:bg-[#1a2632] text-text-main dark:text-white px-6 py-3 rounded-xl font-medium transition-all">
|
||||
<span data-translate="common.cancel">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/income.js') }}"></script>
|
||||
<script>
|
||||
// Mobile menu toggle
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
const mobileMenuClose = document.getElementById('mobile-menu-close');
|
||||
|
||||
if (mobileMenuToggle && mobileMenu) {
|
||||
mobileMenuToggle.addEventListener('click', () => {
|
||||
mobileMenu.classList.remove('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
if (mobileMenuClose && mobileMenu) {
|
||||
mobileMenuClose.addEventListener('click', () => {
|
||||
mobileMenu.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Close mobile menu when clicking outside
|
||||
if (mobileMenu) {
|
||||
mobileMenu.addEventListener('click', (e) => {
|
||||
if (e.target === mobileMenu) {
|
||||
mobileMenu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
121
app/templates/landing.html
Normal file
121
app/templates/landing.html
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FINA - Personal Finance Manager</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<style>
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class'
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 min-h-screen text-white">
|
||||
<!-- Navigation -->
|
||||
<nav class="bg-slate-800/50 backdrop-blur-sm border-b border-slate-700/50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex items-center">
|
||||
<span class="text-2xl font-bold text-blue-400">FINA</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a href="/auth/login" class="text-slate-300 hover:text-blue-400 transition">Login</a>
|
||||
<a href="/auth/register" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition">Get Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20">
|
||||
<div class="text-center">
|
||||
<h1 class="text-5xl font-bold text-white mb-6">
|
||||
Take Control of Your Finances
|
||||
</h1>
|
||||
<p class="text-xl text-slate-300 mb-8 max-w-2xl mx-auto">
|
||||
FINA helps you track expenses, manage budgets, and achieve your financial goals with ease.
|
||||
</p>
|
||||
<div class="flex justify-center space-x-4">
|
||||
<a href="/auth/register" class="bg-blue-600 text-white px-8 py-3 rounded-lg text-lg font-semibold hover:bg-blue-700 transition shadow-lg shadow-blue-500/50">
|
||||
Start Free
|
||||
</a>
|
||||
<a href="/auth/login" class="bg-slate-700 text-white px-8 py-3 rounded-lg text-lg font-semibold border-2 border-slate-600 hover:bg-slate-600 transition">
|
||||
Sign In
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Features -->
|
||||
<div class="grid md:grid-cols-3 gap-8 mt-20">
|
||||
<div class="bg-slate-800/50 backdrop-blur-sm p-8 rounded-xl border border-slate-700/50 hover:border-blue-500/50 transition">
|
||||
<span class="material-icons text-blue-400 text-5xl mb-4">account_balance_wallet</span>
|
||||
<h3 class="text-2xl font-bold mb-3 text-white">Track Expenses</h3>
|
||||
<p class="text-slate-300">Monitor your spending habits and categorize expenses effortlessly.</p>
|
||||
</div>
|
||||
<div class="bg-slate-800/50 backdrop-blur-sm p-8 rounded-xl border border-slate-700/50 hover:border-blue-500/50 transition">
|
||||
<span class="material-icons text-blue-400 text-5xl mb-4">insights</span>
|
||||
<h3 class="text-2xl font-bold mb-3 text-white">Visual Reports</h3>
|
||||
<p class="text-slate-300">Get insights with beautiful charts and detailed financial reports.</p>
|
||||
</div>
|
||||
<div class="bg-slate-800/50 backdrop-blur-sm p-8 rounded-xl border border-slate-700/50 hover:border-blue-500/50 transition">
|
||||
<span class="material-icons text-blue-400 text-5xl mb-4">description</span>
|
||||
<h3 class="text-2xl font-bold mb-3 text-white">Document Management</h3>
|
||||
<p class="text-slate-300">Store and organize receipts and financial documents securely.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Features -->
|
||||
<div class="mt-16 bg-slate-800/50 backdrop-blur-sm rounded-xl border border-slate-700/50 p-8">
|
||||
<h2 class="text-3xl font-bold text-center mb-8 text-white">Why Choose FINA?</h2>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="flex items-start space-x-3">
|
||||
<span class="material-icons text-green-400">check_circle</span>
|
||||
<div>
|
||||
<h4 class="font-semibold text-white">Secure & Private</h4>
|
||||
<p class="text-slate-300">Your financial data is encrypted and protected with 2FA.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start space-x-3">
|
||||
<span class="material-icons text-green-400">check_circle</span>
|
||||
<div>
|
||||
<h4 class="font-semibold text-white">Easy to Use</h4>
|
||||
<p class="text-slate-300">Intuitive interface designed for everyone.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start space-x-3">
|
||||
<span class="material-icons text-green-400">check_circle</span>
|
||||
<div>
|
||||
<h4 class="font-semibold text-white">Mobile Ready</h4>
|
||||
<p class="text-slate-300">Access your finances from any device, anywhere.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start space-x-3">
|
||||
<span class="material-icons text-green-400">check_circle</span>
|
||||
<div>
|
||||
<h4 class="font-semibold text-white">Free to Use</h4>
|
||||
<p class="text-slate-300">No hidden fees, completely free personal finance management.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-slate-800/50 backdrop-blur-sm mt-20 py-8 border-t border-slate-700/50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center text-slate-400">
|
||||
<p>© 2025 FINA. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
241
app/templates/recurring.html
Normal file
241
app/templates/recurring.html
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Recurring Expenses - FINA{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex h-screen w-full">
|
||||
<!-- Side Navigation -->
|
||||
<aside id="sidebar" class="hidden lg:flex w-64 flex-col bg-sidebar-light dark:bg-background-dark border-r border-border-light dark:border-[#233648] transition-all duration-300 shadow-sm dark:shadow-none">
|
||||
<div class="p-6 flex flex-col h-full justify-between">
|
||||
<div class="flex flex-col gap-8">
|
||||
<!-- User Profile -->
|
||||
<div class="flex gap-3 items-center">
|
||||
<div class="relative">
|
||||
<img src="{{ current_user.avatar | avatar_url }}" alt="Avatar" class="size-12 rounded-full object-cover border-2 border-primary">
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h2 class="text-sm font-semibold text-text-main dark:text-white truncate">{{ current_user.username }}</h2>
|
||||
<p class="text-xs text-text-muted dark:text-[#92adc9] flex items-center gap-1">
|
||||
{% if current_user.is_admin %}<span data-translate="user.admin">Admin</span>{% else %}<span data-translate="user.user">User</span>{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Links -->
|
||||
<nav class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.dashboard') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">dashboard</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.dashboard">Dashboard</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.transactions') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">receipt_long</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.transactions">Transactions</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.income') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">payments</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.income">Income</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-primary/10 text-primary border border-primary/10" href="{{ url_for('main.recurring') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">repeat</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.recurring">Recurring</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.import_page') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">file_upload</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.import">Import CSV</span>
|
||||
</a> <a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.reports') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">pie_chart</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.reports">Reports</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.documents') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">folder_open</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.documents">Documents</span>
|
||||
</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="/admin">
|
||||
<span class="material-symbols-outlined text-[20px]">admin_panel_settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.admin">Admin</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Links -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<button id="theme-toggle" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]" id="theme-icon">light_mode</span>
|
||||
<span class="text-sm font-medium" id="theme-text" data-translate="dashboard.lightMode">Light Mode</span>
|
||||
</button>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.settings') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.settings">Settings</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-slate-50 dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('auth.logout') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">logout</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.logout">Log out</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<div class="flex-1 overflow-auto bg-background-light dark:bg-background-dark">
|
||||
<!-- Header -->
|
||||
<div class="bg-white dark:bg-[#0f1419] border-b border-gray-200 dark:border-white/10 sticky top-0 z-10">
|
||||
<div class="max-w-7xl mx-auto px-6 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-text-main dark:text-white" data-translate="recurring.title">Recurring Expenses</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-sm mt-1" data-translate="recurring.subtitle">Manage subscriptions and recurring bills</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button onclick="detectRecurringPatterns()" id="detect-btn"
|
||||
class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg transition-colors flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-[20px]">auto_awesome</span>
|
||||
<span data-translate="recurring.detect">Detect Patterns</span>
|
||||
</button>
|
||||
<button onclick="showAddRecurringModal()"
|
||||
class="px-4 py-2 bg-primary hover:bg-primary-dark text-white rounded-lg transition-colors flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-[20px]">add</span>
|
||||
<span data-translate="recurring.addNew">Add Recurring</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="max-w-7xl mx-auto px-6 py-8">
|
||||
<!-- Suggestions Section (Hidden by default) -->
|
||||
<div id="suggestions-section" class="hidden mb-8">
|
||||
<div class="bg-gradient-to-r from-blue-500/10 to-purple-500/10 border border-blue-500/20 rounded-xl p-6 mb-4">
|
||||
<div class="flex items-start gap-3 mb-4">
|
||||
<span class="material-symbols-outlined text-blue-400 text-[28px]">auto_awesome</span>
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-text-main dark:text-white mb-1" data-translate="recurring.suggestionsTitle">Detected Recurring Patterns</h2>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-sm" data-translate="recurring.suggestionsDesc">We found these potential recurring expenses based on your transaction history</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="suggestions-list" class="space-y-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recurring Expenses List -->
|
||||
<div class="bg-white dark:bg-[#0f1419] border border-gray-200 dark:border-white/10 rounded-xl overflow-hidden">
|
||||
<div id="recurring-list" class="p-6"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Recurring Modal -->
|
||||
<div id="add-recurring-modal" class="hidden fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||
<div class="bg-white dark:bg-[#0f1419] rounded-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
|
||||
<div class="sticky top-0 bg-white dark:bg-[#0f1419] border-b border-gray-200 dark:border-white/10 px-6 py-4 flex items-center justify-between">
|
||||
<h2 id="modal-title" class="text-xl font-bold text-text-main dark:text-white" data-translate="recurring.add">Add Recurring Expense</h2>
|
||||
<button onclick="closeRecurringModal()" class="text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="recurring-form" class="p-6 space-y-5">
|
||||
<input type="hidden" id="recurring-id">
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="recurring.name">
|
||||
Name
|
||||
</label>
|
||||
<input type="text" id="recurring-name" required
|
||||
class="w-full px-4 py-2.5 bg-gray-50 dark:bg-[#1a2632] border border-gray-200 dark:border-white/10 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent text-text-main dark:text-white"
|
||||
placeholder="e.g., Netflix Subscription">
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="form.amount">
|
||||
Amount
|
||||
</label>
|
||||
<input type="number" id="recurring-amount" step="0.01" min="0" required
|
||||
class="w-full px-4 py-2.5 bg-gray-50 dark:bg-[#1a2632] border border-gray-200 dark:border-white/10 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent text-text-main dark:text-white">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="form.category">
|
||||
Category
|
||||
</label>
|
||||
<select id="recurring-category" required
|
||||
class="w-full px-4 py-2.5 bg-gray-50 dark:bg-[#1a2632] border border-gray-200 dark:border-white/10 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent text-text-main dark:text-white">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="recurring.frequency">
|
||||
Frequency
|
||||
</label>
|
||||
<select id="recurring-frequency" required
|
||||
class="w-full px-4 py-2.5 bg-gray-50 dark:bg-[#1a2632] border border-gray-200 dark:border-white/10 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent text-text-main dark:text-white">
|
||||
<option value="daily" data-translate="recurring.frequency.daily">Daily</option>
|
||||
<option value="weekly" data-translate="recurring.frequency.weekly">Weekly</option>
|
||||
<option value="monthly" selected data-translate="recurring.frequency.monthly">Monthly</option>
|
||||
<option value="yearly" data-translate="recurring.frequency.yearly">Yearly</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="day-container" class="hidden">
|
||||
<label id="day-label" class="block text-sm font-medium text-text-main dark:text-white mb-2">
|
||||
Day
|
||||
</label>
|
||||
<input type="number" id="recurring-day" min="1" max="28"
|
||||
class="w-full px-4 py-2.5 bg-gray-50 dark:bg-[#1a2632] border border-gray-200 dark:border-white/10 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent text-text-main dark:text-white">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="recurring.nextDue">
|
||||
Next Due Date
|
||||
</label>
|
||||
<input type="date" id="recurring-next-due" required
|
||||
class="w-full px-4 py-2.5 bg-gray-50 dark:bg-[#1a2632] border border-gray-200 dark:border-white/10 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent text-text-main dark:text-white">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="recurring.notes">
|
||||
Notes (optional)
|
||||
</label>
|
||||
<textarea id="recurring-notes" rows="2"
|
||||
class="w-full px-4 py-2.5 bg-gray-50 dark:bg-[#1a2632] border border-gray-200 dark:border-white/10 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent text-text-main dark:text-white resize-none"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 p-4 bg-blue-500/5 border border-blue-500/20 rounded-lg">
|
||||
<input type="checkbox" id="recurring-auto-create"
|
||||
class="size-5 rounded border-gray-300 text-primary focus:ring-primary">
|
||||
<div class="flex-1">
|
||||
<label for="recurring-auto-create" class="text-sm font-medium text-text-main dark:text-white cursor-pointer" data-translate="recurring.autoCreate">
|
||||
Auto-create expenses
|
||||
</label>
|
||||
<p class="text-xs text-text-muted dark:text-[#92adc9]" data-translate="recurring.autoCreateDesc">
|
||||
Automatically create an expense when due date arrives
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 pt-4">
|
||||
<button type="submit" id="recurring-submit-btn"
|
||||
class="flex-1 px-6 py-3 bg-primary hover:bg-primary-dark text-white rounded-lg font-medium transition-colors">
|
||||
<span data-translate="actions.save">Save</span>
|
||||
</button>
|
||||
<button type="button" onclick="closeRecurringModal()"
|
||||
class="px-6 py-3 bg-gray-100 dark:bg-[#1a2632] hover:bg-gray-200 dark:hover:bg-[#243040] text-text-main dark:text-white rounded-lg font-medium transition-colors">
|
||||
<span data-translate="actions.cancel">Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/recurring.js') }}"></script>
|
||||
{% endblock %}
|
||||
301
app/templates/reports.html
Normal file
301
app/templates/reports.html
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Reports - FINA{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex h-screen w-full">
|
||||
<!-- Sidebar -->
|
||||
<aside id="sidebar" class="hidden lg:flex w-64 flex-col bg-sidebar-light dark:bg-background-dark border-r border-border-light dark:border-[#233648]">
|
||||
<div class="p-6 flex flex-col h-full justify-between">
|
||||
<div class="flex flex-col gap-8">
|
||||
<div class="flex gap-3 items-center">
|
||||
<img src="{{ current_user.avatar | avatar_url }}" alt="{{ current_user.username }}" class="size-10 rounded-full border-2 border-primary/30 object-cover">
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-text-main dark:text-white text-base font-bold leading-none">{{ current_user.username }}</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-xs font-normal mt-1">
|
||||
{% if current_user.is_admin %}<span data-translate="user.admin">Admin</span>{% else %}<span data-translate="user.user">User</span>{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.dashboard') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">dashboard</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.dashboard">Dashboard</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.transactions') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">receipt_long</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.transactions">Transactions</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.income') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">payments</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.income">Income</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.recurring') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">repeat</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.recurring">Recurring</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.import_page') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">file_upload</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.import">Import CSV</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-primary/20 text-primary border border-primary/10" href="{{ url_for('main.reports') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">pie_chart</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.reports">Reports</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.documents') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">folder_open</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.documents">Documents</span>
|
||||
</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.admin') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">admin_panel_settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.admin">Admin</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<button id="theme-toggle" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]" id="theme-icon">light_mode</span>
|
||||
<span class="text-sm font-medium" id="theme-text" data-translate="dashboard.lightMode">Light Mode</span>
|
||||
</button>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.settings') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.settings">Settings</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('auth.logout') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">logout</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.logout">Log out</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative bg-background-light dark:bg-background-dark">
|
||||
<header class="h-16 flex items-center justify-between px-6 lg:px-8 border-b border-border-light dark:border-[#233648] bg-card-light/95 dark:bg-background-dark/80 backdrop-blur z-10 shrink-0">
|
||||
<div class="flex items-center gap-4">
|
||||
<button id="menu-toggle" class="lg:hidden text-text-main dark:text-white">
|
||||
<span class="material-symbols-outlined">menu</span>
|
||||
</button>
|
||||
<h2 class="text-text-main dark:text-white text-lg font-bold" data-translate="reports.title">Financial Reports</h2>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button id="export-report-btn" class="flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white hover:bg-background-light dark:hover:bg-white/5 rounded-lg border border-transparent hover:border-border-light dark:hover:border-[#233648] transition-all">
|
||||
<span class="material-symbols-outlined text-[18px]">download</span>
|
||||
<span class="hidden sm:inline" data-translate="reports.export">Export CSV</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-6 lg:p-8 scroll-smooth">
|
||||
<div class="max-w-7xl mx-auto flex flex-col gap-6 pb-10">
|
||||
<!-- Period Selection -->
|
||||
<div class="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 bg-card-light dark:bg-card-dark p-4 rounded-xl border border-border-light dark:border-[#233648] shadow-sm">
|
||||
<div class="flex items-center gap-3">
|
||||
<h3 class="text-sm font-semibold text-text-muted dark:text-[#92adc9] uppercase tracking-wider" data-translate="reports.analysisPeriod">Analysis Period:</h3>
|
||||
<div class="flex bg-background-light dark:bg-background-dark rounded-lg p-1 border border-border-light dark:border-[#233648]">
|
||||
<button class="period-btn active px-3 py-1 text-sm font-medium rounded transition-colors" data-period="30">
|
||||
<span data-translate="reports.last30Days">Last 30 Days</span>
|
||||
</button>
|
||||
<button class="period-btn px-3 py-1 text-sm font-medium rounded transition-colors" data-period="90">
|
||||
<span data-translate="reports.quarter">Quarter</span>
|
||||
</button>
|
||||
<button class="period-btn px-3 py-1 text-sm font-medium rounded transition-colors" data-period="365">
|
||||
<span data-translate="reports.ytd">YTD</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-3 w-full lg:w-auto">
|
||||
<select id="category-filter" class="px-3 py-2 bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white hover:border-primary/50 transition-colors text-sm w-full lg:w-48">
|
||||
<option value=""><span data-translate="reports.allCategories">All Categories</span></option>
|
||||
</select>
|
||||
<button id="generate-report-btn" class="flex-1 sm:flex-none bg-primary hover:bg-blue-600 text-white h-10 px-4 rounded-lg text-sm font-semibold shadow-lg shadow-primary/20 transition-all flex items-center justify-center gap-2">
|
||||
<span class="material-symbols-outlined text-[18px]">autorenew</span>
|
||||
<span data-translate="reports.generate">Generate Report</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
|
||||
<!-- Total Income -->
|
||||
<div class="bg-card-light dark:bg-card-dark p-5 rounded-xl border border-green-500/20 dark:border-green-500/30 shadow-sm hover:border-green-500/50 transition-colors group">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-text-muted dark:text-[#92adc9] text-xs font-medium uppercase tracking-wider" data-translate="reports.totalIncome">Total Income</span>
|
||||
<h4 id="total-income" class="text-2xl font-bold text-green-600 dark:text-green-400 mt-1">$0.00</h4>
|
||||
</div>
|
||||
<div class="p-2 bg-green-500/10 rounded-lg text-green-600 dark:text-green-400 group-hover:bg-green-500 group-hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]">trending_up</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span id="income-change" class="flex items-center font-medium px-1.5 py-0.5 rounded"></span>
|
||||
<span class="text-text-muted dark:text-[#92adc9]" data-translate="reports.vsLastMonth">vs last period</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-card-light dark:bg-card-dark p-5 rounded-xl border border-border-light dark:border-[#233648] shadow-sm hover:border-primary/30 transition-colors group">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-text-muted dark:text-[#92adc9] text-xs font-medium uppercase tracking-wider" data-translate="reports.totalSpent">Total Spent</span>
|
||||
<h4 id="total-spent" class="text-2xl font-bold text-text-main dark:text-white mt-1">$0.00</h4>
|
||||
</div>
|
||||
<div class="p-2 bg-primary/10 rounded-lg text-primary group-hover:bg-primary group-hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]">payments</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span id="spent-change" class="flex items-center font-medium px-1.5 py-0.5 rounded"></span>
|
||||
<span class="text-text-muted dark:text-[#92adc9]" data-translate="reports.vsLastMonth">vs last period</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profit/Loss -->
|
||||
<div class="bg-card-light dark:bg-card-dark p-5 rounded-xl border border-border-light dark:border-[#233648] shadow-sm hover:border-accent/30 transition-colors group">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-text-muted dark:text-[#92adc9] text-xs font-medium uppercase tracking-wider" data-translate="reports.profitLoss">Profit/Loss</span>
|
||||
<h4 id="profit-loss" class="text-2xl font-bold text-text-main dark:text-white mt-1">$0.00</h4>
|
||||
</div>
|
||||
<div class="p-2 bg-accent/10 rounded-lg text-accent group-hover:bg-accent group-hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]">account_balance</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span id="profit-change" class="flex items-center font-medium px-1.5 py-0.5 rounded"></span>
|
||||
<span class="text-text-muted dark:text-[#92adc9]" data-translate="reports.vsLastMonth">vs last period</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-card-light dark:bg-card-dark p-5 rounded-xl border border-border-light dark:border-[#233648] shadow-sm hover:border-warning/30 transition-colors group">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-text-muted dark:text-[#92adc9] text-xs font-medium uppercase tracking-wider" data-translate="reports.avgDaily">Avg. Daily</span>
|
||||
<h4 id="avg-daily" class="text-2xl font-bold text-text-main dark:text-white mt-1">$0.00</h4>
|
||||
</div>
|
||||
<div class="p-2 bg-warning/10 rounded-lg text-warning group-hover:bg-warning group-hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]">calendar_today</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span id="avg-change" class="flex items-center font-medium px-1.5 py-0.5 rounded"></span>
|
||||
<span class="text-text-muted dark:text-[#92adc9]" data-translate="reports.vsLastMonth">vs last period</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-card-light dark:bg-card-dark p-5 rounded-xl border border-border-light dark:border-[#233648] shadow-sm hover:border-success/30 transition-colors group">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-text-muted dark:text-[#92adc9] text-xs font-medium uppercase tracking-wider" data-translate="reports.savingsRate">Savings Rate</span>
|
||||
<h4 id="savings-rate" class="text-2xl font-bold text-text-main dark:text-white mt-1">0%</h4>
|
||||
</div>
|
||||
<div class="p-2 bg-success/10 rounded-lg text-success group-hover:bg-success group-hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]">savings</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span id="savings-change" class="text-success flex items-center font-medium bg-success/10 px-1.5 py-0.5 rounded">
|
||||
<span class="material-symbols-outlined text-[14px] mr-0.5">arrow_upward</span>
|
||||
0.0%
|
||||
</span>
|
||||
<span class="text-text-muted dark:text-[#92adc9]" data-translate="reports.vsLastMonth">vs last period</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Row -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Income vs Expenses Trend Chart -->
|
||||
<div class="lg:col-span-2 bg-card-light dark:bg-card-dark p-6 rounded-xl border border-border-light dark:border-[#233648] shadow-sm flex flex-col">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-lg font-bold text-text-main dark:text-white" data-translate="reports.incomeVsExpenses">Income vs Expenses</h3>
|
||||
</div>
|
||||
<div class="flex-1 min-h-[300px]">
|
||||
<canvas id="trend-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Income Sources Breakdown -->
|
||||
<div class="lg:col-span-1 bg-card-light dark:bg-card-dark p-5 rounded-xl border border-border-light dark:border-[#233648] shadow-sm flex flex-col">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-base font-bold text-text-main dark:text-white" data-translate="reports.incomeSources">Income Sources</h3>
|
||||
</div>
|
||||
<div class="flex items-center justify-center mb-4">
|
||||
<!-- CSS Conic Gradient Pie Chart for Income -->
|
||||
<div id="income-pie-chart" class="size-40 rounded-full relative transition-all duration-500" style="background: conic-gradient(#10b981 0% 100%);">
|
||||
<!-- Inner hole for donut effect -->
|
||||
<div class="absolute inset-3 bg-card-light dark:bg-card-dark rounded-full flex flex-col items-center justify-center z-10 border border-border-light dark:border-[#233648]">
|
||||
<span class="text-text-muted dark:text-[#92adc9] text-[10px] font-medium" data-translate="dashboard.total">Total</span>
|
||||
<span id="income-pie-total" class="text-green-600 dark:text-green-400 text-base font-bold">0 lei</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="income-legend" class="grid grid-cols-1 gap-y-1.5 max-h-[200px] overflow-y-auto pr-2">
|
||||
<!-- Legend items will be populated by JS -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category & Monthly Comparison Row -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Category Breakdown -->
|
||||
<div class="bg-card-light dark:bg-card-dark p-5 rounded-xl border border-border-light dark:border-[#233648] shadow-sm flex flex-col">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-base font-bold text-text-main dark:text-white" data-translate="reports.categoryBreakdown">Expense Categories</h3>
|
||||
</div>
|
||||
<div class="flex items-center justify-center mb-4">
|
||||
<!-- CSS Conic Gradient Pie Chart -->
|
||||
<div id="category-pie-chart" class="size-40 rounded-full relative transition-all duration-500" style="background: conic-gradient(#233648 0% 100%);">
|
||||
<!-- Inner hole for donut effect -->
|
||||
<div class="absolute inset-3 bg-card-light dark:bg-card-dark rounded-full flex flex-col items-center justify-center z-10 border border-border-light dark:border-[#233648]">
|
||||
<span class="text-text-muted dark:text-[#92adc9] text-[10px] font-medium" data-translate="dashboard.total">Total</span>
|
||||
<span id="category-pie-total" class="text-text-main dark:text-white text-base font-bold">0 lei</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="category-legend" class="grid grid-cols-1 gap-y-1.5 max-h-[200px] overflow-y-auto pr-2">
|
||||
<!-- Legend items will be populated by JS -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Monthly Comparison -->
|
||||
<div class="bg-card-light dark:bg-card-dark p-6 rounded-xl border border-border-light dark:border-[#233648] shadow-sm">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-lg font-bold text-text-main dark:text-white" data-translate="reports.monthlyComparison">Monthly Comparison</h3>
|
||||
</div>
|
||||
<div class="h-64">
|
||||
<canvas id="monthly-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Smart Recommendations -->
|
||||
<div class="bg-card-light dark:bg-card-dark p-6 rounded-xl border border-border-light dark:border-[#233648] shadow-sm flex flex-col">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-lg font-bold text-text-main dark:text-white" data-translate="reports.smartRecommendations">Smart Recommendations</h3>
|
||||
<span class="material-symbols-outlined text-primary text-[20px]">psychology</span>
|
||||
</div>
|
||||
<div id="recommendations-container" class="flex flex-col gap-4">
|
||||
<!-- Loading state -->
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||
<p class="text-sm text-text-muted dark:text-[#92adc9]" data-translate="common.loading">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/reports.js') }}"></script>
|
||||
{% endblock %}
|
||||
250
app/templates/settings.html
Normal file
250
app/templates/settings.html
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Settings - FINA{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex h-screen w-full">
|
||||
<!-- Sidebar -->
|
||||
<aside id="sidebar" class="hidden lg:flex w-64 flex-col bg-sidebar-light dark:bg-background-dark border-r border-border-light dark:border-[#233648]">
|
||||
<div class="p-6 flex flex-col h-full justify-between">
|
||||
<div class="flex flex-col gap-8">
|
||||
<div class="flex gap-3 items-center">
|
||||
<img id="sidebar-avatar" src="{{ current_user.avatar | avatar_url }}" alt="{{ current_user.username }}" class="size-10 rounded-full border-2 border-primary/30 object-cover">
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-text-main dark:text-white text-base font-bold leading-none">{{ current_user.username }}</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-xs font-normal mt-1">
|
||||
{% if current_user.is_admin %}<span data-translate="user.admin">Admin</span>{% else %}<span data-translate="user.user">User</span>{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.dashboard') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">dashboard</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.dashboard">Dashboard</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.transactions') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">receipt_long</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.transactions">Transactions</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.income') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">payments</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.income">Income</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.recurring') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">repeat</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.recurring">Recurring</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.import_page') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">file_upload</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.import">Import CSV</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.reports') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">pie_chart</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.reports">Reports</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.documents') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">folder_open</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.documents">Documents</span>
|
||||
</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.admin') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">admin_panel_settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.admin">Admin</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<button id="theme-toggle" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]" id="theme-icon">light_mode</span>
|
||||
<span class="text-sm font-medium" id="theme-text" data-translate="dashboard.lightMode">Light Mode</span>
|
||||
</button>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-primary/20 text-primary border border-primary/10" href="{{ url_for('main.settings') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.settings">Settings</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('auth.logout') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">logout</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.logout">Log out</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative bg-background-light dark:bg-background-dark">
|
||||
<header class="h-16 flex items-center justify-between px-6 lg:px-8 border-b border-border-light dark:border-[#233648] bg-card-light/95 dark:bg-background-dark/80 backdrop-blur z-10 shrink-0">
|
||||
<div class="flex items-center gap-4">
|
||||
<button id="menu-toggle" class="lg:hidden text-text-main dark:text-white">
|
||||
<span class="material-symbols-outlined">menu</span>
|
||||
</button>
|
||||
<h2 class="text-text-main dark:text-white text-lg font-bold" data-translate="settings.title">Settings</h2>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-6 lg:p-8 scroll-smooth">
|
||||
<div class="max-w-4xl mx-auto flex flex-col gap-6 pb-10">
|
||||
|
||||
<!-- Avatar Section -->
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-xl p-6 shadow-sm">
|
||||
<h3 class="text-lg font-semibold text-text-main dark:text-white mb-4" data-translate="settings.avatar">Profile Avatar</h3>
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-6">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<img id="current-avatar" src="{{ current_user.avatar | avatar_url }}" alt="Current Avatar" class="size-24 rounded-full border-4 border-primary/20 object-cover shadow-md">
|
||||
<input type="file" id="avatar-upload" class="hidden" accept="image/png,image/jpeg,image/jpg,image/gif,image/webp">
|
||||
<button id="upload-avatar-btn" class="px-4 py-2 bg-primary text-white rounded-lg text-sm font-medium hover:bg-primary/90 transition-colors flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-[18px]">upload</span>
|
||||
<span data-translate="settings.uploadAvatar">Upload Custom</span>
|
||||
</button>
|
||||
<p class="text-xs text-text-muted dark:text-[#92adc9] text-center max-w-[200px]" data-translate="settings.avatarDesc">PNG, JPG, GIF, WEBP. Max 20MB</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-text-muted dark:text-[#92adc9] mb-3" data-translate="settings.defaultAvatars">Or choose a default avatar:</p>
|
||||
<div class="grid grid-cols-3 sm:grid-cols-6 gap-3">
|
||||
<button class="default-avatar-btn p-2 rounded-lg border-2 border-transparent hover:border-primary transition-all" data-avatar="icons/avatars/avatar-1.svg">
|
||||
<img src="{{ url_for('static', filename='icons/avatars/avatar-1.svg') }}" alt="Avatar 1" class="w-full h-full rounded-full">
|
||||
</button>
|
||||
<button class="default-avatar-btn p-2 rounded-lg border-2 border-transparent hover:border-primary transition-all" data-avatar="icons/avatars/avatar-2.svg">
|
||||
<img src="{{ url_for('static', filename='icons/avatars/avatar-2.svg') }}" alt="Avatar 2" class="w-full h-full rounded-full">
|
||||
</button>
|
||||
<button class="default-avatar-btn p-2 rounded-lg border-2 border-transparent hover:border-primary transition-all" data-avatar="icons/avatars/avatar-3.svg">
|
||||
<img src="{{ url_for('static', filename='icons/avatars/avatar-3.svg') }}" alt="Avatar 3" class="w-full h-full rounded-full">
|
||||
</button>
|
||||
<button class="default-avatar-btn p-2 rounded-lg border-2 border-transparent hover:border-primary transition-all" data-avatar="icons/avatars/avatar-4.svg">
|
||||
<img src="{{ url_for('static', filename='icons/avatars/avatar-4.svg') }}" alt="Avatar 4" class="w-full h-full rounded-full">
|
||||
</button>
|
||||
<button class="default-avatar-btn p-2 rounded-lg border-2 border-transparent hover:border-primary transition-all" data-avatar="icons/avatars/avatar-5.svg">
|
||||
<img src="{{ url_for('static', filename='icons/avatars/avatar-5.svg') }}" alt="Avatar 5" class="w-full h-full rounded-full">
|
||||
</button>
|
||||
<button class="default-avatar-btn p-2 rounded-lg border-2 border-transparent hover:border-primary transition-all" data-avatar="icons/avatars/avatar-6.svg">
|
||||
<img src="{{ url_for('static', filename='icons/avatars/avatar-6.svg') }}" alt="Avatar 6" class="w-full h-full rounded-full">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profile Settings -->
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-xl p-6 shadow-sm">
|
||||
<h3 class="text-lg font-semibold text-text-main dark:text-white mb-4" data-translate="settings.profile">Profile Information</h3>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="form.username">Username</label>
|
||||
<input type="text" id="username" value="{{ current_user.username }}" class="w-full bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-2.5 text-text-main dark:text-white focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="form.email">Email</label>
|
||||
<input type="email" id="email" value="{{ current_user.email }}" class="w-full bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-2.5 text-text-main dark:text-white focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all">
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="form.language">Language</label>
|
||||
<select id="language" class="w-full bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-2.5 text-text-main dark:text-white focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all">
|
||||
<option value="en" {% if current_user.language == 'en' %}selected{% endif %}>English</option>
|
||||
<option value="ro" {% if current_user.language == 'ro' %}selected{% endif %}>Română</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="form.currency">Currency</label>
|
||||
<select id="currency" class="w-full bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-2.5 text-text-main dark:text-white focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all">
|
||||
<option value="USD" {% if current_user.currency == 'USD' %}selected{% endif %}>USD ($)</option>
|
||||
<option value="EUR" {% if current_user.currency == 'EUR' %}selected{% endif %}>EUR (€)</option>
|
||||
<option value="RON" {% if current_user.currency == 'RON' %}selected{% endif %}>RON (lei)</option>
|
||||
<option value="GBP" {% if current_user.currency == 'GBP' %}selected{% endif %}>GBP (£)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="form.monthlyBudget">Monthly Budget</label>
|
||||
<input type="number" id="monthly-budget" value="{{ current_user.monthly_budget or 0 }}" step="0.01" min="0" class="w-full bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-2.5 text-text-main dark:text-white focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="save-profile-btn" class="w-full md:w-auto px-6 py-2.5 bg-primary text-white rounded-lg font-medium hover:bg-primary/90 transition-colors flex items-center justify-center gap-2">
|
||||
<span class="material-symbols-outlined text-[18px]">save</span>
|
||||
<span data-translate="settings.saveProfile">Save Profile</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Change -->
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-xl p-6 shadow-sm">
|
||||
<h3 class="text-lg font-semibold text-text-main dark:text-white mb-4" data-translate="settings.changePassword">Change Password</h3>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="settings.currentPassword">Current Password</label>
|
||||
<input type="password" id="current-password" class="w-full bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-2.5 text-text-main dark:text-white focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="settings.newPassword">New Password</label>
|
||||
<input type="password" id="new-password" class="w-full bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-2.5 text-text-main dark:text-white focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-text-main dark:text-white mb-2" data-translate="settings.confirmPassword">Confirm New Password</label>
|
||||
<input type="password" id="confirm-password" class="w-full bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] rounded-lg px-4 py-2.5 text-text-main dark:text-white focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all">
|
||||
</div>
|
||||
|
||||
<button id="change-password-btn" class="w-full md:w-auto px-6 py-2.5 bg-primary text-white rounded-lg font-medium hover:bg-primary/90 transition-colors flex items-center justify-center gap-2">
|
||||
<span class="material-symbols-outlined text-[18px]">lock_reset</span>
|
||||
<span data-translate="settings.updatePassword">Update Password</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2FA Settings -->
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-xl p-6 shadow-sm">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-text-main dark:text-white mb-1" data-translate="settings.twoFactor">Two-Factor Authentication</h3>
|
||||
<p class="text-sm text-text-muted dark:text-[#92adc9]">
|
||||
{% if current_user.two_factor_enabled %}
|
||||
<span data-translate="settings.twoFactorEnabled">2FA is currently enabled for your account</span>
|
||||
{% else %}
|
||||
<span data-translate="settings.twoFactorDisabled">Add an extra layer of security to your account</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<span class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-sm font-medium {% if current_user.two_factor_enabled %}bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-400{% else %}bg-slate-100 dark:bg-white/10 text-text-muted dark:text-[#92adc9]{% endif %}">
|
||||
<span class="material-symbols-outlined text-[16px]">{% if current_user.two_factor_enabled %}verified_user{% else %}lock{% endif %}</span>
|
||||
<span data-translate="{% if current_user.two_factor_enabled %}settings.enabled{% else %}settings.disabled{% endif %}">{% if current_user.two_factor_enabled %}Enabled{% else %}Disabled{% endif %}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
{% if current_user.two_factor_enabled %}
|
||||
<a href="{{ url_for('auth.setup_2fa') }}" class="inline-flex items-center justify-center gap-2 px-4 py-2 bg-background-light dark:bg-background-dark border border-border-light dark:border-[#233648] text-text-main dark:text-white rounded-lg text-sm font-medium hover:bg-slate-100 dark:hover:bg-white/5 transition-colors">
|
||||
<span class="material-symbols-outlined text-[18px]">refresh</span>
|
||||
<span data-translate="settings.regenerateCodes">Regenerate Backup Codes</span>
|
||||
</a>
|
||||
<form method="POST" action="{{ url_for('auth.disable_2fa') }}" class="inline-block">
|
||||
<button type="submit" onclick="return confirm('Are you sure you want to disable 2FA?')" class="inline-flex items-center justify-center gap-2 px-4 py-2 bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/30 text-red-600 dark:text-red-400 rounded-lg text-sm font-medium hover:bg-red-100 dark:hover:bg-red-500/20 transition-colors">
|
||||
<span class="material-symbols-outlined text-[18px]">lock_open</span>
|
||||
<span data-translate="settings.disable2FA">Disable 2FA</span>
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<a href="{{ url_for('auth.setup_2fa') }}" class="inline-flex items-center justify-center gap-2 px-4 py-2 bg-primary text-white rounded-lg text-sm font-medium hover:bg-primary/90 transition-colors">
|
||||
<span class="material-symbols-outlined text-[18px]">lock</span>
|
||||
<span data-translate="settings.enable2FA">Enable 2FA</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/settings.js') }}"></script>
|
||||
{% endblock %}
|
||||
270
app/templates/transactions.html
Normal file
270
app/templates/transactions.html
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Transactions - FINA{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex h-screen w-full">
|
||||
<!-- Sidebar (reuse from dashboard) -->
|
||||
<aside id="sidebar" class="hidden lg:flex w-64 flex-col bg-sidebar-light dark:bg-background-dark border-r border-border-light dark:border-[#233648]">
|
||||
<div class="p-6 flex flex-col h-full justify-between">
|
||||
<div class="flex flex-col gap-8">
|
||||
<div class="flex gap-3 items-center">
|
||||
<img src="{{ current_user.avatar | avatar_url }}" alt="{{ current_user.username }}" class="size-10 rounded-full border-2 border-primary/30 object-cover">
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-text-main dark:text-white text-base font-bold leading-none">{{ current_user.username }}</h1>
|
||||
<p class="text-text-muted dark:text-[#92adc9] text-xs font-normal mt-1">
|
||||
{% if current_user.is_admin %}<span data-translate="user.admin">Admin</span>{% else %}<span data-translate="user.user">User</span>{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.dashboard') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">dashboard</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.dashboard">Dashboard</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg bg-primary/20 text-primary border border-primary/10" href="{{ url_for('main.transactions') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">receipt_long</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.transactions">Transactions</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.income') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">payments</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.income">Income</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.recurring') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">repeat</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.recurring">Recurring</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.import_page') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">file_upload</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.import">Import CSV</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.reports') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">pie_chart</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.reports">Reports</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.documents') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">folder_open</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.documents">Documents</span>
|
||||
</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.admin') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">admin_panel_settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.admin">Admin</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<button id="theme-toggle" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined text-[20px]" id="theme-icon">light_mode</span>
|
||||
<span class="text-sm font-medium" id="theme-text" data-translate="dashboard.lightMode">Light Mode</span>
|
||||
</button>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('main.settings') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">settings</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.settings">Settings</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-text-muted dark:text-[#92adc9] hover:bg-background-light dark:hover:bg-[#233648] hover:text-text-main dark:hover:text-white transition-colors" href="{{ url_for('auth.logout') }}">
|
||||
<span class="material-symbols-outlined text-[20px]">logout</span>
|
||||
<span class="text-sm font-medium" data-translate="nav.logout">Log out</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="flex-1 flex flex-col h-full overflow-hidden relative bg-background-light dark:bg-background-dark">
|
||||
<header class="h-16 flex items-center justify-between px-6 lg:px-8 border-b border-border-light dark:border-[#233648] bg-card-light/95 dark:bg-background-dark/95 backdrop-blur z-10 shrink-0">
|
||||
<div class="flex items-center gap-4">
|
||||
<button id="menu-toggle" class="lg:hidden text-text-main dark:text-white">
|
||||
<span class="material-symbols-outlined">menu</span>
|
||||
</button>
|
||||
<h2 class="text-text-main dark:text-white text-lg font-bold" data-translate="transactions.title">Transactions</h2>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button id="export-csv-btn" class="bg-background-light dark:bg-[#1a2632] border border-border-light dark:border-[#233648] text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white h-9 px-4 rounded-lg text-sm font-medium transition-colors flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-[18px]">download</span>
|
||||
<span class="hidden sm:inline" data-translate="transactions.export">Export CSV</span>
|
||||
</button>
|
||||
<button id="import-csv-btn" class="bg-background-light dark:bg-[#1a2632] border border-border-light dark:border-[#233648] text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white h-9 px-4 rounded-lg text-sm font-medium transition-colors flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-[18px]">upload</span>
|
||||
<span class="hidden sm:inline" data-translate="transactions.import">Import CSV</span>
|
||||
</button>
|
||||
<button id="add-expense-btn" class="bg-primary hover:bg-primary/90 text-white h-9 px-4 rounded-lg text-sm font-semibold shadow-lg shadow-primary/20 transition-all flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-[18px]">add</span>
|
||||
<span class="hidden sm:inline" data-translate="transactions.addExpense">Add Expense</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-6 lg:p-8 bg-background-light dark:bg-background-dark">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<!-- Transactions Card -->
|
||||
<div class="bg-card-light dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-xl overflow-hidden">
|
||||
<!-- Header with Search and Filters -->
|
||||
<div class="p-6 border-b border-border-light dark:border-[#233648]">
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-between items-start sm:items-center">
|
||||
<!-- Search Bar -->
|
||||
<div class="relative flex-1 max-w-md">
|
||||
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-text-muted dark:text-[#92adc9] text-[20px]">search</span>
|
||||
<input
|
||||
type="text"
|
||||
id="filter-search"
|
||||
data-translate="transactions.search"
|
||||
placeholder="Search transactions..."
|
||||
class="w-full bg-background-light dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg pl-10 pr-4 py-2 text-text-main dark:text-white text-sm placeholder-text-muted dark:placeholder-[#5f7a96] focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Filter Buttons -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button id="date-filter-btn" class="flex items-center gap-2 px-3 py-2 bg-background-light dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white hover:border-primary/50 transition-colors text-sm">
|
||||
<span class="material-symbols-outlined text-[18px]">calendar_today</span>
|
||||
<span data-translate="transactions.date">Date</span>
|
||||
</button>
|
||||
<select id="filter-category" class="px-3 py-2 bg-background-light dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white hover:border-primary/50 transition-colors text-sm">
|
||||
<option value="" data-translate="transactions.allCategories">Category</option>
|
||||
</select>
|
||||
<button id="more-filters-btn" class="flex items-center gap-2 px-3 py-2 bg-background-light dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white hover:border-primary/50 transition-colors text-sm">
|
||||
<span class="material-symbols-outlined text-[18px]">tune</span>
|
||||
<span data-translate="transactions.filters">Filters</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Filters (Hidden by default) -->
|
||||
<div id="advanced-filters" class="hidden mt-4 pt-4 border-t border-border-light dark:border-[#233648]">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="transactions.startDate">Start Date</label>
|
||||
<input type="date" id="filter-start-date" class="w-full bg-background-light dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="transactions.endDate">End Date</label>
|
||||
<input type="date" id="filter-end-date" class="w-full bg-background-light dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transactions Table -->
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-background-light dark:bg-[#0f1419]">
|
||||
<tr class="border-b border-border-light dark:border-[#233648]">
|
||||
<th class="p-5 text-left text-text-muted dark:text-[#92adc9] text-sm font-medium" data-translate="transactions.tableTransaction">Transaction</th>
|
||||
<th class="p-5 text-left text-text-muted dark:text-[#92adc9] text-sm font-medium" data-translate="transactions.tableCategory">Category</th>
|
||||
<th class="p-5 text-left text-text-muted dark:text-[#92adc9] text-sm font-medium" data-translate="transactions.tableDate">Date</th>
|
||||
<th class="p-5 text-left text-text-muted dark:text-[#92adc9] text-sm font-medium" data-translate="transactions.tablePayment">Payment</th>
|
||||
<th class="p-5 text-right text-text-muted dark:text-[#92adc9] text-sm font-medium" data-translate="transactions.tableAmount">Amount</th>
|
||||
<th class="p-5 text-center text-text-muted dark:text-[#92adc9] text-sm font-medium" data-translate="transactions.tableStatus">Status</th>
|
||||
<th class="p-5 text-right text-text-muted dark:text-[#92adc9] text-sm font-medium" data-translate="transactions.tableActions">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="transactions-list" class="divide-y divide-border-light dark:divide-[#233648]">
|
||||
<!-- Transactions will be loaded here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination Footer -->
|
||||
<div class="p-4 border-t border-border-light dark:border-[#233648] flex flex-col sm:flex-row gap-4 justify-between items-center bg-card-light dark:bg-card-dark">
|
||||
<span class="text-sm text-text-muted dark:text-[#92adc9]">
|
||||
<span data-translate="transactions.showing">Showing</span> <span id="page-start" class="text-text-main dark:text-white font-medium">1</span> <span data-translate="transactions.to">to</span>
|
||||
<span id="page-end" class="text-text-main dark:text-white font-medium">10</span> <span data-translate="transactions.of">of</span>
|
||||
<span id="total-count" class="text-text-main dark:text-white font-medium">0</span> <span data-translate="transactions.results">results</span>
|
||||
</span>
|
||||
<div id="pagination" class="flex gap-2">
|
||||
<!-- Pagination buttons will be added here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Hidden file input for CSV import -->
|
||||
<input type="file" id="csv-file-input" accept=".csv" class="hidden">
|
||||
|
||||
<!-- Add/Edit Expense Modal -->
|
||||
<div id="expense-modal" class="hidden fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||
<div class="bg-white dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-2xl max-w-md w-full max-h-[90vh] overflow-y-auto shadow-xl">
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 id="expense-modal-title" class="text-text-main dark:text-white text-xl font-bold" data-translate="modal.add_expense">Add Expense</h3>
|
||||
<button id="close-expense-modal" class="text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="expense-form" class="space-y-4">
|
||||
<input type="hidden" id="expense-id" name="expense_id">
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.amount">Amount</label>
|
||||
<input type="number" step="0.01" name="amount" required class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.description">Description</label>
|
||||
<input type="text" name="description" required class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.category">Category</label>
|
||||
<select name="category_id" required class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
<option value="" data-translate="dashboard.selectCategory">Select category...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.date">Date</label>
|
||||
<input type="date" name="date" required class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.tags">Tags (comma separated)</label>
|
||||
<input type="text" name="tags" class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-text-muted dark:text-[#92adc9] text-sm mb-2 block" data-translate="form.receipt">Receipt (optional)</label>
|
||||
<div id="current-receipt-info" class="hidden mb-2 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-blue-600 dark:text-blue-400 text-[20px]">attach_file</span>
|
||||
<span class="text-sm text-blue-900 dark:text-blue-100" data-translate="form.currentReceipt">Current receipt attached</span>
|
||||
</div>
|
||||
<button type="button" id="view-current-receipt" class="text-blue-600 dark:text-blue-400 hover:underline text-sm" data-translate="transactions.viewReceipt">View</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" name="receipt" id="receipt-input" accept="image/*,.pdf" class="w-full bg-slate-50 dark:bg-[#111a22] border border-border-light dark:border-[#233648] rounded-lg px-4 py-2 text-text-main dark:text-white focus:border-primary focus:ring-1 focus:ring-primary file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-primary file:text-white hover:file:bg-primary/90">
|
||||
<p class="text-xs text-text-muted dark:text-[#92adc9] mt-1" data-translate="form.receiptHelp">Upload a new file to replace existing receipt</p>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="expense-submit-btn" class="w-full bg-primary hover:bg-primary/90 text-white py-3 rounded-lg font-semibold transition-colors shadow-md" data-translate="actions.save">Save Expense</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Receipt Viewer Modal -->
|
||||
<div id="receipt-modal" class="hidden fixed inset-0 bg-black/70 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||
<div class="bg-white dark:bg-card-dark border border-border-light dark:border-[#233648] rounded-2xl max-w-4xl w-full max-h-[90vh] overflow-hidden shadow-2xl">
|
||||
<div class="p-6 border-b border-border-light dark:border-[#233648] flex justify-between items-center">
|
||||
<h3 class="text-text-main dark:text-white text-xl font-bold">Receipt</h3>
|
||||
<button id="close-receipt-modal" class="text-text-muted dark:text-[#92adc9] hover:text-text-main dark:hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-6 overflow-auto max-h-[calc(90vh-120px)]">
|
||||
<div id="receipt-content" class="flex items-center justify-center min-h-[400px]">
|
||||
<!-- Receipt content will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/transactions.js') }}"></script>
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue