Responsive Page
Responsive Page
DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Personal Finance Manager</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/
all.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-color: #2c3e50;
--secondary-color: #3498db;
--success-color: #27ae60;
--danger-color: #e74c3c;
--warning-color: #f39c12;
--info-color: #17a2b8;
--light-bg: #ecf0f1;
--dark-bg: #34495e;
--white: #ffffff;
--text-dark: #2c3e50;
--text-light: #7f8c8d;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: var(--text-dark);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
/* Navigation */
.navbar {
background: rgba(44, 62, 80, 0.95);
backdrop-filter: blur(10px);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
box-shadow: var(--shadow);
}
.nav-container {
max-width: 1400px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
}
.logo {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1.5rem;
font-weight: bold;
color: var(--white);
text-decoration: none;
}
.nav-menu {
display: flex;
list-style: none;
gap: 1rem;
}
.nav-link {
color: var(--white);
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 5px;
transition: all 0.3s ease;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
}
.nav-link:hover,
.nav-link.active {
background: var(--secondary-color);
transform: translateY(-2px);
}
.hamburger {
display: none;
flex-direction: column;
cursor: pointer;
gap: 4px;
}
.hamburger span {
width: 25px;
height: 3px;
background: var(--white);
transition: 0.3s;
}
/* Main Content */
.main-content {
margin-top: 80px;
min-height: calc(100vh - 80px);
padding: 2rem;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
.section {
display: none;
}
.section.active {
display: block;
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Cards */
.card {
background: var(--white);
border-radius: 10px;
box-shadow: var(--shadow);
padding: 1.5rem;
margin-bottom: 1.5rem;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--light-bg);
}
.card-title {
font-size: 1.5rem;
font-weight: bold;
color: var(--primary-color);
display: flex;
align-items: center;
gap: 0.5rem;
}
/* Dashboard Stats */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: var(--white);
border-radius: 10px;
padding: 1.5rem;
text-align: center;
box-shadow: var(--shadow);
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--success-color), var(--
secondary-color));
}
.stat-icon {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.stat-value {
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.stat-label {
color: var(--text-light);
font-size: 0.9rem;
}
.income {
color: var(--success-color);
}
.expense {
color: var(--danger-color);
}
.balance {
color: var(--info-color);
}
.savings {
color: var(--warning-color);
}
/* Forms */
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: var(--text-dark);
}
.form-control {
width: 100%;
padding: 0.75rem;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 1rem;
transition: border-color 0.3s ease;
}
.form-control:focus {
outline: none;
border-color: var(--secondary-color);
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}
.form-control.error {
border-color: var(--danger-color);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.checkbox-group,
.radio-group {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.checkbox-item,
.radio-item {
display: flex;
align-items: center;
gap: 0.5rem;
}
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 5px;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-weight: 600;
}
.btn-primary {
background: var(--secondary-color);
color: var(--white);
}
.btn-success {
background: var(--success-color);
color: var(--white);
}
.btn-danger {
background: var(--danger-color);
color: var(--white);
}
.btn-warning {
background: var(--warning-color);
color: var(--white);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
/* Tables */
.table-container {
overflow-x: auto;
border-radius: 10px;
box-shadow: var(--shadow);
}
.table {
width: 100%;
border-collapse: collapse;
background: var(--white);
}
.table th,
.table td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid #eee;
}
.table th {
background: var(--light-bg);
font-weight: 600;
color: var(--primary-color);
}
/* Error Messages */
.error-message {
color: var(--danger-color);
font-size: 0.875rem;
margin-top: 0.25rem;
display: none;
}
.error-message.show {
display: block;
}
/* Success Messages */
.success-message {
background: var(--success-color);
color: var(--white);
padding: 1rem;
border-radius: 5px;
margin-bottom: 1rem;
display: none;
}
.success-message.show {
display: block;
animation: slideDown 0.3s ease;
}
@keyframes slideDown {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.chart-container canvas {
max-height: 250px !important;
}
}
/* Loading Spinner */
.loading {
display: none;
text-align: center;
padding: 2rem;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid var(--secondary-color);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Budget Progress */
.budget-item {
background: #f8f9fa;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
border-left: 4px solid var(--secondary-color);
}
.budget-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.budget-name {
font-weight: bold;
color: var(--primary-color);
}
.budget-amount {
color: var(--text-light);
font-size: 0.9rem;
}
.progress-bar {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
margin: 0.5rem 0;
}
.progress-fill {
height: 100%;
background: var(--success-color);
transition: width 0.3s ease;
}
.progress-fill.warning {
background: var(--warning-color);
}
.progress-fill.danger {
background: var(--danger-color);
}
/* Goal Progress */
.goal-item {
background: #f8f9fa;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
border-left: 4px solid var(--warning-color);
}
.goal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.goal-name {
font-weight: bold;
color: var(--primary-color);
}
.goal-priority {
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: bold;
}
.priority-low {
background: #d4edda;
color: #155724;
}
.priority-medium {
background: #fff3cd;
color: #856404;
}
.priority-high {
background: #f8d7da;
color: #721c24;
}
/* Responsive Design */
@media (max-width: 768px) {
.nav-menu {
position: fixed;
top: 80px;
left: -100%;
width: 100%;
height: calc(100vh - 80px);
background: var(--primary-color);
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding-top: 2rem;
transition: left 0.3s ease;
}
.nav-menu.active {
left: 0;
}
.hamburger {
display: flex;
}
.hamburger.active span:nth-child(1) {
transform: rotate(45deg) translate(5px, 5px);
}
.hamburger.active span:nth-child(2) {
opacity: 0;
}
.hamburger.active span:nth-child(3) {
transform: rotate(-45deg) translate(7px, -6px);
}
.main-content {
padding: 1rem;
}
.stats-grid {
grid-template-columns: 1fr;
}
.form-grid {
grid-template-columns: 1fr;
}
.form-row {
grid-template-columns: 1fr;
}
.nav-container {
padding: 1rem;
}
}
.stat-card {
padding: 1rem;
}
.stat-icon {
font-size: 2rem;
}
.stat-value {
font-size: 1.5rem;
}
}
</style>
</head>
<body>
<!-- Navigation -->
<nav class="navbar">
<div class="nav-container">
<a href="#" class="logo">
<i class="fas fa-wallet"></i>
Finance Manager
</a>
<ul class="nav-menu">
<li><a href="#" class="nav-link active" data-section="dashboard">
<i class="fas fa-chart-pie"></i>Dashboard
</a></li>
<li><a href="#" class="nav-link" data-section="transactions">
<i class="fas fa-exchange-alt"></i>Transakcje
</a></li>
<li><a href="#" class="nav-link" data-section="budgets">
<i class="fas fa-calculator"></i>Budżety
</a></li>
<li><a href="#" class="nav-link" data-section="goals">
<i class="fas fa-bullseye"></i>Cele
</a></li>
<li><a href="#" class="nav-link" data-section="reports">
<i class="fas fa-chart-bar"></i>Raporty
</a></li>
<li><a href="#" class="nav-link" data-section="settings">
<i class="fas fa-cog"></i>Ustawienia
</a></li>
</ul>
<div class="hamburger" id="hamburger">
<span></span>
<span></span>
<span></span>
</div>
</div>
</nav>
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-chart-line"></i>
Przegląd miesięczny
</h2>
<button class="btn btn-primary" id="refresh-data">
<i class="fas fa-sync-alt"></i>Odśwież dane
</button>
</div>
<div class="chart-container">
<canvas id="monthly-chart" width="400"
height="200"></canvas>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-clock"></i>
Ostatnie transakcje
</h2>
<a href="#" class="btn btn-primary" data-
section="transactions">Zobacz wszystkie</a>
</div>
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Data</th>
<th>Opis</th>
<th>Kategoria</th>
<th>Kwota</th>
</tr>
</thead>
<tbody id="recent-transactions">
<tr>
<td colspan="4" style="text-align: center;
color: var(--text-light);">
Brak transakcji do wyświetlenia
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="transaction-
amount">Kwota *</label>
<input type="number" id="transaction-amount"
class="form-control" placeholder="0.00"
step="0.01" min="0.01" required>
<div class="error-message" id="amount-
error">Wprowadź poprawną kwotę</div>
</div>
<div class="form-group">
<label class="form-label" for="transaction-
date">Data *</label>
<input type="date" id="transaction-date"
class="form-control" required>
<div class="error-message" id="date-
error">Wybierz datę transakcji</div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="transaction-
category">Kategoria *</label>
<select id="transaction-category" class="form-
control" required>
<option value="">Wybierz kategorię</option>
<optgroup label="Przychody">
<option
value="salary">Wynagrodzenie</option>
<option value="business">Działalność
gospodarcza</option>
<option
value="investment">Inwestycje</option>
<option value="gift">Prezenty</option>
<option value="other-income">Inne
przychody</option>
</optgroup>
<optgroup label="Wydatki">
<option value="food">Jedzenie</option>
<option
value="transport">Transport</option>
<option value="housing">Mieszkanie</option>
<option
value="entertainment">Rozrywka</option>
<option value="healthcare">Zdrowie</option>
<option value="shopping">Zakupy</option>
<option value="bills">Rachunki</option>
<option value="other-expense">Inne
wydatki</option>
</optgroup>
</select>
<div class="error-message" id="category-
error">Wybierz kategorię</div>
</div>
<div class="form-group">
<label class="form-label" for="transaction-
description">Opis</label>
<textarea id="transaction-description" class="form-
control"
placeholder="Opcjonalny opis transakcji..."
rows="3"></textarea>
</div>
<div class="form-group">
<label class="form-label">Dodatkowe opcje</label>
<div class="checkbox-group">
<div class="checkbox-item">
<input type="checkbox" id="recurring-
transaction">
<label for="recurring-
transaction">Transakcja cykliczna</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="important-
transaction">
<label for="important-transaction">Ważna
transakcja</label>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-list"></i>
Wszystkie transakcje
</h2>
<button class="btn btn-warning" id="clear-
transactions">
<i class="fas fa-trash"></i>Wyczyść wszystkie
</button>
</div>
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Data</th>
<th>Typ</th>
<th>Opis</th>
<th>Kategoria</th>
<th>Kwota</th>
<th>Akcje</th>
</tr>
</thead>
<tbody id="all-transactions">
<tr>
<td colspan="6" style="text-align: center;
color: var(--text-light);">
Brak transakcji do wyświetlenia
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="budget-
amount">Kwota budżetu *</label>
<input type="number" id="budget-amount"
class="form-control" placeholder="0.00"
step="0.01" min="0.01" required>
<div class="error-message" id="budget-amount-
error">Wprowadź kwotę budżetu</div>
</div>
<div class="form-group">
<label class="form-label" for="budget-
period">Okres *</label>
<select id="budget-period" class="form-control"
required>
<option value="">Wybierz okres</option>
<option value="weekly">Tygodniowy</option>
<option value="monthly">Miesięczny</option>
<option
value="quarterly">Kwartalny</option>
<option value="yearly">Roczny</option>
</select>
<div class="error-message" id="budget-period-
error">Wybierz okres budżetu</div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="budget-
category">Kategoria budżetu</label>
<select id="budget-category" class="form-control">
<option value="all">Wszystkie
kategorie</option>
<option value="food">Jedzenie</option>
<option value="transport">Transport</option>
<option value="housing">Mieszkanie</option>
<option value="entertainment">Rozrywka</option>
<option value="healthcare">Zdrowie</option>
<option value="shopping">Zakupy</option>
<option value="bills">Rachunki</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Powiadomienia</label>
<div class="checkbox-group">
<div class="checkbox-item">
<input type="checkbox" id="budget-alert-
50">
<label for="budget-alert-50">Powiadom przy
50% budżetu</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="budget-alert-
80">
<label for="budget-alert-80">Powiadom przy
80% budżetu</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="budget-alert-
100">
<label for="budget-alert-100">Powiadom przy
przekroczeniu</label>
</div>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="goal-
amount">Docelowa kwota *</label>
<input type="number" id="goal-amount"
class="form-control" placeholder="0.00"
step="0.01" min="0.01" required>
<div class="error-message" id="goal-amount-
error">Wprowadź docelową kwotę</div>
</div>
<div class="form-group">
<label class="form-label" for="goal-
deadline">Termin realizacji</label>
<input type="date" id="goal-deadline"
class="form-control">
</div>
</div>
<div class="form-group">
<label class="form-label" for="goal-
current">Aktualna kwota</label>
<input type="number" id="goal-current" class="form-
control" placeholder="0.00"
step="0.01" min="0">
</div>
<div class="form-group">
<label class="form-label" for="goal-
priority">Priorytet</label>
<div class="radio-group">
<div class="radio-item">
<input type="radio" id="priority-low"
name="goal-priority" value="low">
<label for="priority-low">Niski</label>
</div>
<div class="radio-item">
<input type="radio" id="priority-medium"
name="goal-priority" value="medium"
checked>
<label for="priority-medium">Średni</label>
</div>
<div class="radio-item">
<input type="radio" id="priority-high"
name="goal-priority" value="high">
<label for="priority-high">Wysoki</label>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="goal-
description">Opis celu</label>
<textarea id="goal-description" class="form-
control"
placeholder="Opisz swój cel finansowy..."
rows="3"></textarea>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-bullseye"></i>
Moje cele finansowe
</h2>
</div>
<div id="goals-list">
<p style="text-align: center; color: var(--text-light);
padding: 2rem;">
Brak utworzonych celów finansowych
</p>
</div>
</div>
</div>
</section>
<div class="chart-container">
<h3 style="margin-bottom: 1rem;">Trend wydatków w
czasie</h3>
<canvas id="expenses-trend-chart" width="400"
height="200"></canvas>
</div>
</div>
</div>
</section>
<div class="form-group">
<label class="form-label" for="default-
currency">Domyślna waluta</label>
<select id="default-currency" class="form-control">
<option value="PLN">PLN - Polski złoty</option>
<option value="EUR">EUR - Euro</option>
<option value="USD">USD - Dolar
amerykański</option>
<option value="GBP">GBP - Funt
brytyjski</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Powiadomienia</label>
<div class="checkbox-group">
<div class="checkbox-item">
<input type="checkbox" id="email-
notifications" checked>
<label for="email-
notifications">Powiadomienia email</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="budget-
notifications" checked>
<label for="budget-notifications">Alerty
budżetowe</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="goal-
notifications">
<label for="goal-
notifications">Przypomnienia o celach</label>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="data-backup">Backup
danych</label>
<div style="display: flex; gap: 1rem; flex-wrap:
wrap;">
<button type="button" class="btn btn-primary"
id="export-data">
<i class="fas fa-download"></i>Eksportuj
dane
</button>
<button type="button" class="btn btn-warning"
id="import-data">
<i class="fas fa-upload"></i>Importuj dane
</button>
<input type="file" id="import-file"
accept=".json" style="display: none;">
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-database"></i>
Zarządzanie danymi
</h2>
</div>
<div class="form-group">
<label class="form-label">Statystyki aplikacji</label>
<div class="stats-grid" style="grid-template-columns:
1fr 1fr;">
<div class="stat-card">
<div class="stat-value" id="total-transactions-
count">0</div>
<div class="stat-label">Transakcji</div>
</div>
<div class="stat-card">
<div class="stat-value" id="total-budgets-
count">0</div>
<div class="stat-label">Budżetów</div>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">Akcje</label>
<div style="display: flex; gap: 1rem; flex-wrap:
wrap;">
<button type="button" class="btn btn-danger"
id="clear-all-data">
<i class="fas fa-trash-alt"></i>Wyczyść
wszystkie dane
</button>
<button type="button" class="btn btn-warning"
id="reset-app">
<i class="fas fa-redo"></i>Resetuj aplikację
</button>
</div>
</div>
</div>
</div>
</section>
</div>
</main>
<script>
// Data storage management
class DataManager {
constructor() {
this.transactions = this.getFromStorage('transactions') || [];
this.budgets = this.getFromStorage('budgets') || [];
this.goals = this.getFromStorage('goals') || [];
this.settings = this.getFromStorage('settings') ||
this.getDefaultSettings();
this.loadSampleData();
}
getFromStorage(key) {
try {
const data = localStorage.getItem(`finance_${key}`);
return data ? JSON.parse(data) : null;
} catch (e) {
console.error('Error loading data from storage:', e);
return null;
}
}
saveToStorage(key, data) {
try {
localStorage.setItem(`finance_${key}`, JSON.stringify(data));
} catch (e) {
console.error('Error saving data to storage:', e);
}
}
getDefaultSettings() {
return {
userName: '',
userEmail: '',
currency: 'PLN',
emailNotifications: true,
budgetNotifications: true,
goalNotifications: false
};
}
loadSampleData() {
if (this.transactions.length === 0) {
this.loadSampleTransactions();
}
}
async loadSampleTransactions() {
setTimeout(() => {
const sampleTransactions = [
{
id: Date.now() + 1,
type: 'income',
amount: 5000,
date: '2024-01-15',
category: 'salary',
description: 'Wynagrodzenie miesięczne',
recurring: true,
important: true
},
{
id: Date.now() + 2,
type: 'expense',
amount: 150,
date: '2024-01-16',
category: 'food',
description: 'Zakupy spożywcze',
recurring: false,
important: false
},
{
id: Date.now() + 3,
type: 'expense',
amount: 80,
date: '2024-01-17',
category: 'transport',
description: 'Paliwo do samochodu',
recurring: false,
important: false
}
];
this.transactions =
[...this.transactions, ...sampleTransactions];
this.saveTransactions();
if (window.app) {
app.updateDashboard();
app.renderTransactions();
}
}, 1000);
}
// Transactions
addTransaction(transaction) {
transaction.id = Date.now();
this.transactions.unshift(transaction);
this.saveTransactions();
}
deleteTransaction(id) {
this.transactions = this.transactions.filter(t => t.id !== id);
this.saveTransactions();
}
saveTransactions() {
this.saveToStorage('transactions', this.transactions);
}
clearTransactions() {
this.transactions = [];
this.saveTransactions();
}
// Budgets
addBudget(budget) {
budget.id = Date.now();
budget.spent = 0;
this.budgets.push(budget);
this.saveBudgets();
}
deleteBudget(id) {
this.budgets = this.budgets.filter(b => b.id !== id);
this.saveBudgets();
}
saveBudgets() {
this.saveToStorage('budgets', this.budgets);
}
// Goals
addGoal(goal) {
goal.id = Date.now();
this.goals.push(goal);
this.saveGoals();
}
deleteGoal(id) {
this.goals = this.goals.filter(g => g.id !== id);
this.saveGoals();
}
saveGoals() {
this.saveToStorage('goals', this.goals);
}
// Settings
saveSettings() {
this.saveToStorage('settings', this.settings);
}
// Statistics
getTotalIncome() {
return this.transactions
.filter(t => t.type === 'income')
.reduce((sum, t) => sum + t.amount, 0);
}
getTotalExpenses() {
return this.transactions
.filter(t => t.type === 'expense')
.reduce((sum, t) => sum + t.amount, 0);
}
getCurrentBalance() {
return this.getTotalIncome() - this.getTotalExpenses();
}
// Data export/import
exportData() {
const data = {
transactions: this.transactions,
budgets: this.budgets,
goals: this.goals,
settings: this.settings,
exportDate: new Date().toISOString()
};
importData(fileContent) {
try {
const data = JSON.parse(fileContent);
if (data.transactions) this.transactions = data.transactions;
if (data.budgets) this.budgets = data.budgets;
if (data.goals) this.goals = data.goals;
if (data.settings) this.settings = data.settings;
this.saveTransactions();
this.saveBudgets();
this.saveGoals();
this.saveSettings();
return true;
} catch (e) {
console.error('Error importing data:', e);
return false;
}
}
clearAllData() {
this.transactions = [];
this.budgets = [];
this.goals = [];
this.settings = this.getDefaultSettings();
localStorage.removeItem('finance_transactions');
localStorage.removeItem('finance_budgets');
localStorage.removeItem('finance_goals');
localStorage.removeItem('finance_settings');
}
}
init() {
this.setupNavigation();
this.setupForms();
this.setupEventListeners();
this.updateDashboard();
this.renderTransactions();
this.renderBudgets();
this.renderGoals();
this.loadSettings();
// Add charts initialization
setTimeout(() => this.initializeCharts(), 100);
}
setupNavigation() {
const hamburger = document.getElementById('hamburger');
const navMenu = document.querySelector('.nav-menu');
const navLinks = document.querySelectorAll('.nav-link');
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('active');
navMenu.classList.toggle('active');
});
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const section = link.dataset.section;
if (section) {
this.showSection(section);
navLinks.forEach(l => l.classList.remove('active'));
link.classList.add('active');
hamburger.classList.remove('active');
navMenu.classList.remove('active');
}
});
});
}
showSection(sectionName) {
document.querySelectorAll('.section').forEach(section => {
section.classList.remove('active');
});
setupForms() {
const transactionForm = document.getElementById('transaction-
form');
if (transactionForm) {
transactionForm.addEventListener('submit', (e) => {
e.preventDefault();
this.handleTransactionSubmit();
});
}
setupEventListeners() {
// Dashboard refresh
const refreshBtn = document.getElementById('refresh-data');
if (refreshBtn) {
refreshBtn.addEventListener('click', () => {
this.updateDashboard();
this.showSuccessMessage('Dane zostały odświeżone!');
});
}
// Clear transactions
const clearBtn = document.getElementById('clear-transactions');
if (clearBtn) {
clearBtn.addEventListener('click', () => {
if (confirm('Czy na pewno chcesz usunąć wszystkie
transakcje?')) {
this.dataManager.clearTransactions();
this.updateDashboard();
this.renderTransactions();
this.showSuccessMessage('Wszystkie transakcje zostały
usunięte!');
}
});
}
// Generate report
const reportBtn = document.getElementById('generate-report');
if (reportBtn) {
reportBtn.addEventListener('click', () => {
this.generateReport();
});
}
// Data management
const exportBtn = document.getElementById('export-data');
if (exportBtn) {
exportBtn.addEventListener('click', () => {
this.dataManager.exportData();
this.showSuccessMessage('Dane zostały wyeksportowane!');
});
}
handleTransactionSubmit() {
const form = document.getElementById('transaction-form');
const formData = new FormData(form);
if (!this.validateTransactionForm()) {
return;
}
const transaction = {
type: formData.get('transaction-type'),
amount: parseFloat(document.getElementById('transaction-
amount').value),
date: document.getElementById('transaction-date').value,
category: document.getElementById('transaction-
category').value,
description: document.getElementById('transaction-
description').value,
recurring: document.getElementById('recurring-
transaction').checked,
important: document.getElementById('important-
transaction').checked
};
this.dataManager.addTransaction(transaction);
this.updateDashboard();
this.renderTransactions();
this.showSuccessMessage('Transakcja została dodana!', 'transaction-
success');
form.reset();
validateTransactionForm() {
let isValid = true;
document.querySelectorAll('.error-message').forEach(error => {
error.classList.remove('show');
});
document.querySelectorAll('.form-control').forEach(input => {
input.classList.remove('error');
});
return isValid;
}
handleBudgetSubmit() {
const form = document.getElementById('budget-form');
if (!this.validateBudgetForm()) {
return;
}
const budget = {
name: document.getElementById('budget-name').value,
amount: parseFloat(document.getElementById('budget-
amount').value),
period: document.getElementById('budget-period').value,
category: document.getElementById('budget-category').value,
alert50: document.getElementById('budget-alert-50').checked,
alert80: document.getElementById('budget-alert-80').checked,
alert100: document.getElementById('budget-alert-100').checked
};
this.dataManager.addBudget(budget);
this.renderBudgets();
this.showSuccessMessage('Budżet został utworzony!', 'budget-
success');
form.reset();
}
validateBudgetForm() {
let isValid = true;
return isValid;
}
handleGoalSubmit() {
const form = document.getElementById('goal-form');
if (!this.validateGoalForm()) {
return;
}
const goal = {
name: document.getElementById('goal-name').value,
targetAmount: parseFloat(document.getElementById('goal-
amount').value),
currentAmount: parseFloat(document.getElementById('goal-
current').value) || 0,
deadline: document.getElementById('goal-deadline').value,
priority: document.querySelector('input[name="goal-
priority"]:checked').value,
description: document.getElementById('goal-description').value
};
this.dataManager.addGoal(goal);
this.renderGoals();
this.showSuccessMessage('Cel finansowy został dodany!', 'goal-
success');
form.reset();
document.getElementById('priority-medium').checked = true;
}
validateGoalForm() {
let isValid = true;
return isValid;
}
handleSettingsSubmit() {
this.dataManager.settings = {
userName: document.getElementById('user-name').value,
userEmail: document.getElementById('user-email').value,
currency: document.getElementById('default-currency').value,
emailNotifications: document.getElementById('email-
notifications').checked,
budgetNotifications: document.getElementById('budget-
notifications').checked,
goalNotifications: document.getElementById('goal-
notifications').checked
};
this.dataManager.saveSettings();
this.showSuccessMessage('Ustawienia zostały zapisane!', 'settings-
success');
}
showError(errorId, inputId) {
const errorElement = document.getElementById(errorId);
const inputElement = document.getElementById(inputId);
if (errorElement) {
errorElement.classList.add('show');
}
if (inputElement) {
inputElement.classList.add('error');
}
}
updateDashboard() {
const totalIncome = this.dataManager.getTotalIncome();
const totalExpenses = this.dataManager.getTotalExpenses();
const currentBalance = this.dataManager.getCurrentBalance();
const totalSavings = Math.max(0, currentBalance * 0.1); // 10% of
balance as savings
document.getElementById('total-income').textContent =
this.formatCurrency(totalIncome);
document.getElementById('total-expenses').textContent =
this.formatCurrency(totalExpenses);
document.getElementById('current-balance').textContent =
this.formatCurrency(currentBalance);
document.getElementById('total-savings').textContent =
this.formatCurrency(totalSavings);
// Update statistics
document.getElementById('total-transactions-count').textContent =
this.dataManager.transactions.length;
document.getElementById('total-budgets-count').textContent =
this.dataManager.budgets.length;
this.updateCharts();
}
renderRecentTransactions() {
const tbody = document.getElementById('recent-transactions');
const recentTransactions = this.dataManager.transactions.slice(0,
5);
if (recentTransactions.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="4" style="text-align: center; color:
var(--text-light);">
Brak transakcji do wyświetlenia
</td>
</tr>
`;
return;
}
renderTransactions() {
const tbody = document.getElementById('all-transactions');
if (!tbody) return;
if (this.dataManager.transactions.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="6" style="text-align: center; color:
var(--text-light);">
Brak transakcji do wyświetlenia
</td>
</tr>
`;
return;
}
renderBudgets() {
const container = document.getElementById('budgets-list');
if (!container) return;
if (this.dataManager.budgets.length === 0) {
container.innerHTML = `
<p style="text-align: center; color: var(--text-light);
padding: 2rem;">
Brak utworzonych budżetów
</p>
`;
return;
}
return `
<div class="budget-item">
<div class="budget-header">
<div class="budget-name">${budget.name}</div>
<div class="budget-amount">
${this.formatCurrency(spent)} / $
{this.formatCurrency(budget.amount)}
</div>
</div>
<div class="progress-bar">
<div class="progress-fill ${progressClass}"
style="width: ${Math.min(percentage, 100)}%"></div>
</div>
<div style="display: flex; justify-content: space-
between; align-items: center; margin-top: 0.5rem;">
<small style="color: var(--text-light);">
${this.getPeriodName(budget.period)} • $
{this.getCategoryName(budget.category)}
</small>
<button class="btn btn-danger btn-sm"
onclick="app.deleteBudget(${budget.id})">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`;
}).join('');
}
renderGoals() {
const container = document.getElementById('goals-list');
if (!container) return;
if (this.dataManager.goals.length === 0) {
container.innerHTML = `
<p style="text-align: center; color: var(--text-light);
padding: 2rem;">
Brak utworzonych celów finansowych
</p>
`;
return;
}
return `
<div class="goal-item">
<div class="goal-header">
<div class="goal-name">${goal.name}</div>
<div class="goal-priority priority-$
{goal.priority}">
${this.getPriorityName(goal.priority)}
</div>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: $
{Math.min(percentage, 100)}%"></div>
</div>
<div style="margin-top: 0.5rem;">
<div style="display: flex; justify-content: space-
between;">
<span>$
{this.formatCurrency(goal.currentAmount)} / $
{this.formatCurrency(goal.targetAmount)}</span>
<span>${percentage.toFixed(1)}%</span>
</div>
${remaining > 0 ? `<small style="color: var(--text-
light);">Pozostało: ${this.formatCurrency(remaining)}</small>` : ''}
${goal.deadline ? `<small style="color: var(--text-
light); display: block;">Termin: ${this.formatDate(goal.deadline)}</small>` : ''}
${goal.description ? `<p style="margin-top: 0.5rem;
font-size: 0.9rem;">${goal.description}</p>` : ''}
</div>
<div style="margin-top: 1rem;">
<button class="btn btn-danger btn-sm"
onclick="app.deleteGoal(${goal.id})">
<i class="fas fa-trash"></i> Usuń
</button>
</div>
</div>
`;
}).join('');
}
loadSettings() {
const settings = this.dataManager.settings;
document.getElementById('user-name').value = settings.userName ||
'';
document.getElementById('user-email').value = settings.userEmail ||
'';
document.getElementById('default-currency').value =
settings.currency || 'PLN';
document.getElementById('email-notifications').checked =
settings.emailNotifications;
document.getElementById('budget-notifications').checked =
settings.budgetNotifications;
document.getElementById('goal-notifications').checked =
settings.goalNotifications;
}
generateReport() {
const loading = document.getElementById('report-loading');
const content = document.getElementById('report-content');
loading.style.display = 'block';
content.style.display = 'none';
setTimeout(() => {
loading.style.display = 'none';
content.style.display = 'block';
createMonthlyChart() {
const ctx = document.getElementById('monthly-chart');
if (!ctx) return;
createExpensesPieChart() {
const ctx = document.getElementById('expenses-pie-chart');
if (!ctx) return;
createExpensesTrendChart() {
const ctx = document.getElementById('expenses-trend-chart');
if (!ctx) return;
getMonthlyData() {
const months = ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip',
'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'];
const currentYear = new Date().getFullYear();
const monthlyIncome = new Array(12).fill(0);
const monthlyExpenses = new Array(12).fill(0);
this.dataManager.transactions.forEach(transaction => {
const date = new Date(transaction.date);
if (date.getFullYear() === currentYear) {
const month = date.getMonth();
if (transaction.type === 'income') {
monthlyIncome[month] += transaction.amount;
} else {
monthlyExpenses[month] += transaction.amount;
}
}
});
return {
labels: months,
income: monthlyIncome,
expenses: monthlyExpenses
};
}
getCategoryData() {
const categories = {};
this.dataManager.transactions
.filter(t => t.type === 'expense')
.forEach(transaction => {
const categoryName =
this.getCategoryName(transaction.category);
categories[categoryName] = (categories[categoryName] || 0)
+ transaction.amount;
});
return {
labels: Object.keys(categories),
values: Object.values(categories)
};
}
getTrendData() {
const last6Months = [];
const monthlyIncome = [];
const monthlyExpenses = [];
monthlyIncome.push(income);
monthlyExpenses.push(expenses);
}
return {
labels: last6Months,
income: monthlyIncome,
expenses: monthlyExpenses
};
}
updateCharts() {
if (this.monthlyChart) {
const monthlyData = this.getMonthlyData();
this.monthlyChart.data.datasets[0].data = monthlyData.income;
this.monthlyChart.data.datasets[1].data = monthlyData.expenses;
this.monthlyChart.update();
}
if (this.expensesPieChart) {
const categoryData = this.getCategoryData();
this.expensesPieChart.data.labels = categoryData.labels;
this.expensesPieChart.data.datasets[0].data =
categoryData.values;
this.expensesPieChart.update();
}
if (this.expensesTrendChart) {
const trendData = this.getTrendData();
this.expensesTrendChart.data.labels = trendData.labels;
this.expensesTrendChart.data.datasets[0].data =
trendData.expenses;
this.expensesTrendChart.data.datasets[1].data =
trendData.income;
this.expensesTrendChart.update();
}
}
toggleChartScale(chartId) {
let chart;
switch (chartId) {
case 'monthly-chart':
chart = this.monthlyChart;
break;
case 'expenses-trend-chart':
chart = this.expensesTrendChart;
break;
default:
return;
}
if (chart) {
const currentMax = chart.options.scales.y.max;
if (currentMax) {
// Remove max limit
chart.options.scales.y.max = undefined;
chart.options.scales.y.ticks.maxTicksLimit = 10;
} else {
// Set reasonable max limit
const data = chart.data.datasets.flatMap(dataset =>
dataset.data);
const maxValue = Math.max(...data);
chart.options.scales.y.max = maxValue * 1.2;
chart.options.scales.y.ticks.maxTicksLimit = 6;
}
chart.update();
}
}
// Helper methods
formatCurrency(amount) {
return new Intl.NumberFormat('pl-PL', {
style: 'currency',
currency: this.dataManager.settings.currency || 'PLN'
}).format(amount);
}
formatDate(dateString) {
return new Date(dateString).toLocaleDateString('pl-PL');
}
getCategoryName(category) {
const categories = {
'salary': 'Wynagrodzenie',
'business': 'Działalność',
'investment': 'Inwestycje',
'gift': 'Prezenty',
'other-income': 'Inne przychody',
'food': 'Jedzenie',
'transport': 'Transport',
'housing': 'Mieszkanie',
'entertainment': 'Rozrywka',
'healthcare': 'Zdrowie',
'shopping': 'Zakupy',
'bills': 'Rachunki',
'other-expense': 'Inne wydatki',
'all': 'Wszystkie kategorie'
};
return categories[category] || category;
}
getPeriodName(period) {
const periods = {
'weekly': 'Tygodniowy',
'monthly': 'Miesięczny',
'quarterly': 'Kwartalny',
'yearly': 'Roczny'
};
return periods[period] || period;
}
getPriorityName(priority) {
const priorities = {
'low': 'Niski',
'medium': 'Średni',
'high': 'Wysoki'
};
return priorities[priority] || priority;
}
calculateBudgetSpent(budget) {
if (budget.category === 'all') {
return this.dataManager.transactions
.filter(t => t.type === 'expense')
.reduce((sum, t) => sum + t.amount, 0);
} else {
return this.dataManager.transactions
.filter(t => t.type === 'expense' && t.category ===
budget.category)
.reduce((sum, t) => sum + t.amount, 0);
}
}
// Delete methods
deleteTransaction(id) {
if (confirm('Czy na pewno chcesz usunąć tę transakcję?')) {
this.dataManager.deleteTransaction(id);
this.updateDashboard();
this.renderTransactions();
this.showSuccessMessage('Transakcja została usunięta!');
}
}
deleteBudget(id) {
if (confirm('Czy na pewno chcesz usunąć ten budżet?')) {
this.dataManager.deleteBudget(id);
this.renderBudgets();
this.showSuccessMessage('Budżet został usunięty!');
}
}
deleteGoal(id) {
if (confirm('Czy na pewno chcesz usunąć ten cel?')) {
this.dataManager.deleteGoal(id);
this.renderGoals();
this.showSuccessMessage('Cel został usunięty!');
}
}
}
.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.btn-sm i {
font-size: 0.75rem;
}
.table th,
.table td {
padding: 0.5rem;
}
.btn {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.card-header {
flex-direction: column;
gap: 1rem;
align-items: flex-start;
}
.budget-header,
.goal-header {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
}
/* Loading states */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: none;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loading-overlay.show {
display: flex;
}
/* Notification styles */
.notification {
position: fixed;
top: 100px;
right: 20px;
background: var(--success-color);
color: white;
padding: 1rem;
border-radius: 5px;
box-shadow: var(--shadow);
transform: translateX(100%);
transition: transform 0.3s ease;
z-index: 1001;
}
.notification.show {
transform: translateX(0);
}
.notification.error {
background: var(--danger-color);
}
.notification.warning {
background: var(--warning-color);
}
.form-control:valid {
border-color: var(--success-color);
}
/* Print styles */
@media print {
.navbar,
.btn,
.hamburger {
display: none !important;
}
.main-content {
margin-top: 0;
padding: 1rem;
}
.card {
box-shadow: none;
border: 1px solid #ddd;
break-inside: avoid;
}
.section {
display: block !important;
}
}
</style>
</body>
</html>