0% found this document useful (0 votes)
13 views48 pages

Responsive Page

The document is an HTML template for a Personal Finance Manager application, featuring a responsive design with a navigation bar and various sections for managing finances. It includes styles for cards, forms, tables, and charts, as well as animations and error/success message displays. The layout is optimized for both desktop and mobile devices, ensuring a user-friendly experience.

Uploaded by

geca.dawid21
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views48 pages

Responsive Page

The document is an HTML template for a Personal Finance Manager application, featuring a responsive design with a navigation bar and various sections for managing finances. It includes styles for cards, forms, tables, and charts, as well as animations and error/success message displays. The layout is optimized for both desktop and mobile devices, ensuring a user-friendly experience.

Uploaded by

geca.dawid21
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 48

<!

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);
}

.table tbody tr:hover {


background: #f8f9fa;
}

/* 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 improvements */


.chart-container {
background: var(--white);
border-radius: 10px;
padding: 1.5rem;
box-shadow: var(--shadow);
margin-bottom: 2rem;
height: 400px;
/* Fixed height */
position: relative;
}
.chart-container canvas {
max-height: 350px !important;
}

/* Responsive chart heights */


@media (max-width: 768px) {
.chart-container {
height: 300px;
}

.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;
}
}

@media (max-width: 480px) {


.card {
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>

<!-- Main Content -->


<main class="main-content">
<div class="container">
<!-- Dashboard Section -->
<section id="dashboard" class="section active">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon income">
<i class="fas fa-arrow-up"></i>
</div>
<div class="stat-value income" id="total-income">0,00
zł</div>
<div class="stat-label">Całkowite przychody</div>
</div>
<div class="stat-card">
<div class="stat-icon expense">
<i class="fas fa-arrow-down"></i>
</div>
<div class="stat-value expense" id="total-expenses">0,00
zł</div>
<div class="stat-label">Całkowite wydatki</div>
</div>
<div class="stat-card">
<div class="stat-icon balance">
<i class="fas fa-balance-scale"></i>
</div>
<div class="stat-value balance" id="current-balance">0,00
zł</div>
<div class="stat-label">Saldo bieżące</div>
</div>
<div class="stat-card">
<div class="stat-icon savings">
<i class="fas fa-piggy-bank"></i>
</div>
<div class="stat-value savings" id="total-savings">0,00
zł</div>
<div class="stat-label">Oszczędności</div>
</div>
</div>

<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>

<!-- Transactions Section -->


<section id="transactions" class="section">
<div class="form-grid">
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-plus-circle"></i>
Dodaj transakcję
</h2>
</div>
<div class="success-message"
id="transaction-success"></div>
<form id="transaction-form">
<div class="form-group">
<label class="form-label" for="transaction-
type">Typ transakcji *</label>
<div class="radio-group">
<div class="radio-item">
<input type="radio" id="income-type"
name="transaction-type" value="income"
required>
<label for="income-type">Przychód</label>
</div>
<div class="radio-item">
<input type="radio" id="expense-type"
name="transaction-type" value="expense"
required>
<label for="expense-type">Wydatek</label>
</div>
</div>
<div class="error-message" id="type-error">Wybierz
typ transakcji</div>
</div>

<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>

<button type="submit" class="btn btn-success">


<i class="fas fa-save"></i>Zapisz transakcję
</button>
</form>
</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>

<!-- Budgets Section -->


<section id="budgets" class="section">
<div class="form-grid">
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-plus-circle"></i>
Utwórz budżet
</h2>
</div>
<div class="success-message" id="budget-success"></div>
<form id="budget-form">
<div class="form-group">
<label class="form-label" for="budget-name">Nazwa
budżetu *</label>
<input type="text" id="budget-name" class="form-
control"
placeholder="np. Budżet miesięczny" required>
<div class="error-message" id="budget-name-
error">Wprowadź nazwę budżetu</div>
</div>

<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>

<button type="submit" class="btn btn-success">


<i class="fas fa-save"></i>Utwórz budżet
</button>
</form>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-list"></i>
Aktywne budżety
</h2>
</div>
<div id="budgets-list">
<p style="text-align: center; color: var(--text-light);
padding: 2rem;">
Brak utworzonych budżetów
</p>
</div>
</div>
</div>
</section>

<!-- Goals Section -->


<section id="goals" class="section">
<div class="form-grid">
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-plus-circle"></i>
Dodaj cel finansowy
</h2>
</div>
<div class="success-message" id="goal-success"></div>
<form id="goal-form">
<div class="form-group">
<label class="form-label" for="goal-name">Nazwa
celu *</label>
<input type="text" id="goal-name" class="form-
control"
placeholder="np. Wakacje, Nowy samochód"
required>
<div class="error-message" id="goal-name-
error">Wprowadź nazwę celu</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>

<button type="submit" class="btn btn-success">


<i class="fas fa-save"></i>Dodaj cel
</button>
</form>
</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>

<!-- Reports Section -->


<section id="reports" class="section">
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-chart-bar"></i>
Raporty finansowe
</h2>
<button class="btn btn-primary" id="generate-report">
<i class="fas fa-sync-alt"></i>Wygeneruj raport
</button>
</div>
<div class="loading" id="report-loading">
<div class="spinner"></div>
<p>Generowanie raportu...</p>
</div>
<div id="report-content">
<div class="chart-container">
<h3 style="margin-bottom: 1rem;">Wydatki według
kategorii</h3>
<canvas id="expenses-pie-chart" width="400"
height="200"></canvas>
</div>

<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>

<!-- Settings Section -->


<section id="settings" class="section">
<div class="form-grid">
<div class="card">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-user-cog"></i>
Ustawienia profilu
</h2>
</div>
<div class="success-message" id="settings-success"></div>
<form id="settings-form">
<div class="form-row">
<div class="form-group">
<label class="form-label" for="user-name">Imię
i nazwisko</label>
<input type="text" id="user-name" class="form-
control" placeholder="Jan Kowalski">
</div>
<div class="form-group">
<label class="form-label" for="user-
email">Email</label>
<input type="email" id="user-email"
class="form-control"
placeholder="[email protected]">
</div>
</div>

<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>

<button type="submit" class="btn btn-success">


<i class="fas fa-save"></i>Zapisz ustawienia
</button>
</form>
</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()
};

const blob = new Blob([JSON.stringify(data, null, 2)], { type:


'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `finance_backup_${new Date().toISOString().split('T')
[0]}.json`;
a.click();
URL.revokeObjectURL(url);
}

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');
}
}

// Main application class


class FinanceApp {
constructor() {
this.dataManager = new DataManager();
this.currentSection = 'dashboard';
this.init();
}

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');
});

const targetSection = document.getElementById(sectionName);


if (targetSection) {
targetSection.classList.add('active');
this.currentSection = sectionName;
}
}

setupForms() {
const transactionForm = document.getElementById('transaction-
form');
if (transactionForm) {
transactionForm.addEventListener('submit', (e) => {
e.preventDefault();
this.handleTransactionSubmit();
});
}

const budgetForm = document.getElementById('budget-form');


if (budgetForm) {
budgetForm.addEventListener('submit', (e) => {
e.preventDefault();
this.handleBudgetSubmit();
});
}

const goalForm = document.getElementById('goal-form');


if (goalForm) {
goalForm.addEventListener('submit', (e) => {
e.preventDefault();
this.handleGoalSubmit();
});
}

const settingsForm = document.getElementById('settings-form');


if (settingsForm) {
settingsForm.addEventListener('submit', (e) => {
e.preventDefault();
this.handleSettingsSubmit();
});
}

// Set current date as default


const today = new Date().toISOString().split('T')[0];
const dateInput = document.getElementById('transaction-date');
if (dateInput) {
dateInput.value = today;
}
}

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!');
});
}

const importBtn = document.getElementById('import-data');


if (importBtn) {
importBtn.addEventListener('click', () => {
document.getElementById('import-file').click();
});
}

const importFile = document.getElementById('import-file');


if (importFile) {
importFile.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
if (this.dataManager.importData(e.target.result)) {
this.updateDashboard();
this.renderTransactions();
this.renderBudgets();
this.renderGoals();
this.showSuccessMessage('Dane zostały
zaimportowane!');
} else {
alert('Błąd podczas importu danych!');
}
};
reader.readAsText(file);
}
});
}

const clearAllBtn = document.getElementById('clear-all-data');


if (clearAllBtn) {
clearAllBtn.addEventListener('click', () => {
if (confirm('Czy na pewno chcesz usunąć wszystkie dane? Ta
operacja jest nieodwracalna!')) {
this.dataManager.clearAllData();
this.updateDashboard();
this.renderTransactions();
this.renderBudgets();
this.renderGoals();
this.showSuccessMessage('Wszystkie dane zostały
usunięte!');
}
});
}

const resetBtn = document.getElementById('reset-app');


if (resetBtn) {
resetBtn.addEventListener('click', () => {
if (confirm('Czy na pewno chcesz zresetować aplikację?')) {
location.reload();
}
});
}
}

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();

const today = new Date().toISOString().split('T')[0];


document.getElementById('transaction-date').value = today;
}

validateTransactionForm() {
let isValid = true;

document.querySelectorAll('.error-message').forEach(error => {
error.classList.remove('show');
});
document.querySelectorAll('.form-control').forEach(input => {
input.classList.remove('error');
});

const type = document.querySelector('input[name="transaction-


type"]:checked');
if (!type) {
this.showError('type-error', 'transaction-type');
isValid = false;
}

const amount = document.getElementById('transaction-amount').value;


if (!amount || parseFloat(amount) <= 0) {
this.showError('amount-error', 'transaction-amount');
isValid = false;
}

const date = document.getElementById('transaction-date').value;


if (!date) {
this.showError('date-error', 'transaction-date');
isValid = false;
}

const category = document.getElementById('transaction-


category').value;
if (!category) {
this.showError('category-error', 'transaction-category');
isValid = false;
}

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;

const name = document.getElementById('budget-name').value;


if (!name.trim()) {
this.showError('budget-name-error', 'budget-name');
isValid = false;
}

const amount = document.getElementById('budget-amount').value;


if (!amount || parseFloat(amount) <= 0) {
this.showError('budget-amount-error', 'budget-amount');
isValid = false;
}

const period = document.getElementById('budget-period').value;


if (!period) {
this.showError('budget-period-error', 'budget-period');
isValid = false;
}

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;

const name = document.getElementById('goal-name').value;


if (!name.trim()) {
this.showError('goal-name-error', 'goal-name');
isValid = false;
}

const amount = document.getElementById('goal-amount').value;


if (!amount || parseFloat(amount) <= 0) {
this.showError('goal-amount-error', 'goal-amount');
isValid = false;
}

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');
}
}

showSuccessMessage(message, containerId = null) {


if (containerId) {
const container = document.getElementById(containerId);
if (container) {
container.textContent = message;
container.classList.add('show');
setTimeout(() => container.classList.remove('show'), 3000);
}
} else {
// Show general success message
console.log(message);
}
}

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 recent transactions


this.renderRecentTransactions();

// 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;
}

tbody.innerHTML = recentTransactions.map(transaction => `


<tr>
<td>${this.formatDate(transaction.date)}</td>
<td>${transaction.description || 'Brak opisu'}</td>
<td>${this.getCategoryName(transaction.category)}</td>
<td class="${transaction.type}">$
{this.formatCurrency(transaction.amount)}</td>
</tr>
`).join('');
}

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;
}

tbody.innerHTML = this.dataManager.transactions.map(transaction =>


`
<tr>
<td>${this.formatDate(transaction.date)}</td>
<td>
<span class="badge ${transaction.type === 'income' ?
'income' : 'expense'}">
${transaction.type === 'income' ? 'Przychód' :
'Wydatek'}
</span>
</td>
<td>${transaction.description || 'Brak opisu'}</td>
<td>${this.getCategoryName(transaction.category)}</td>
<td class="${transaction.type}">$
{this.formatCurrency(transaction.amount)}</td>
<td>
<button class="btn btn-danger btn-sm"
onclick="app.deleteTransaction(${transaction.id})">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
`).join('');
}

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;
}

container.innerHTML = this.dataManager.budgets.map(budget => {


const spent = this.calculateBudgetSpent(budget);
const percentage = (spent / budget.amount) * 100;
const progressClass = percentage >= 100 ? 'danger' : percentage
>= 80 ? 'warning' : '';

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;
}

container.innerHTML = this.dataManager.goals.map(goal => {


const percentage = (goal.currentAmount / goal.targetAmount) *
100;
const remaining = goal.targetAmount - goal.currentAmount;

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';

// Destroy existing charts before creating new ones


if (this.expensesPieChart) {
this.expensesPieChart.destroy();
this.expensesPieChart = null;
}
if (this.expensesTrendChart) {
this.expensesTrendChart.destroy();
this.expensesTrendChart = null;
}

// Create new charts


setTimeout(() => {
this.createExpensesPieChart();
this.createExpensesTrendChart();
}, 100);

this.showSuccessMessage('Raport został wygenerowany!');


}, 2000);
}

// Add these methods to the FinanceApp class


initializeCharts() {
this.createMonthlyChart();
this.createExpensesPieChart();
this.createExpensesTrendChart();
}

createMonthlyChart() {
const ctx = document.getElementById('monthly-chart');
if (!ctx) return;

const monthlyData = this.getMonthlyData();

this.monthlyChart = new Chart(ctx, {


type: 'bar',
data: {
labels: monthlyData.labels,
datasets: [{
label: 'Przychody',
data: monthlyData.income,
backgroundColor: 'rgba(39, 174, 96, 0.8)',
borderColor: 'rgba(39, 174, 96, 1)',
borderWidth: 1
}, {
label: 'Wydatki',
data: monthlyData.expenses,
backgroundColor: 'rgba(231, 76, 60, 0.8)',
borderColor: 'rgba(231, 76, 60, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: {
padding: 10
},
scales: {
y: {
beginAtZero: true,
// Dynamic step size based on max value
ticks: {
maxTicksLimit: 8, // Limit number of ticks
callback: function (value) {
return new Intl.NumberFormat('pl-PL', {
style: 'currency',
currency: 'PLN',
notation: value >= 1000000 ?
'compact' : 'standard',
maximumFractionDigits: 0
}).format(value);
},
// Dynamic step size
stepSize: function () {
const maxValue =
Math.max(...monthlyData.income, ...monthlyData.expenses);
if (maxValue > 100000) return
Math.ceil(maxValue / 100000) * 10000;
if (maxValue > 10000) return
Math.ceil(maxValue / 10000) * 1000;
if (maxValue > 1000) return
Math.ceil(maxValue / 1000) * 100;
return Math.ceil(maxValue / 100) * 10;
}()
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function (context) {
return context.dataset.label + ': ' +
new Intl.NumberFormat('pl-PL', {
style: 'currency',
currency: 'PLN'
}).format(context.parsed.y);
}
}
},
legend: {
position: 'top'
}
}
}
});
}

createExpensesPieChart() {
const ctx = document.getElementById('expenses-pie-chart');
if (!ctx) return;

// Destroy existing chart if it exists


if (this.expensesPieChart) {
this.expensesPieChart.destroy();
}

const categoryData = this.getCategoryData();

this.expensesPieChart = new Chart(ctx, {


type: 'pie',
data: {
labels: categoryData.labels,
datasets: [{
data: categoryData.values,
backgroundColor: [
'#FF6384',
'#36A2EB',
'#FFCE56',
'#4BC0C0',
'#9966FF',
'#FF9F40',
'#FF6384',
'#C9CBCF'
],
borderWidth: 2,
borderColor: '#fff'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
callbacks: {
label: function (context) {
const total =
context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((context.parsed /
total) * 100).toFixed(1);
return context.label + ': ' +
new Intl.NumberFormat('pl-PL', {
style: 'currency',
currency: 'PLN'
}).format(context.parsed) + ` ($
{percentage}%)`;
}
}
}
}
}
});
}

createExpensesTrendChart() {
const ctx = document.getElementById('expenses-trend-chart');
if (!ctx) return;

// Destroy existing chart if it exists


if (this.expensesTrendChart) {
this.expensesTrendChart.destroy();
}

const trendData = this.getTrendData();


const maxValue =
Math.max(...trendData.expenses, ...trendData.income);

this.expensesTrendChart = new Chart(ctx, {


type: 'line',
data: {
labels: trendData.labels,
datasets: [{
label: 'Wydatki',
data: trendData.expenses,
borderColor: 'rgba(231, 76, 60, 1)',
backgroundColor: 'rgba(231, 76, 60, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.4
}, {
label: 'Przychody',
data: trendData.income,
borderColor: 'rgba(39, 174, 96, 1)',
backgroundColor: 'rgba(39, 174, 96, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
layout: {
padding: 10
},
scales: {
y: {
beginAtZero: true,
max: maxValue * 1.1, // Add 10% padding to top
ticks: {
maxTicksLimit: 6, // Limit number of ticks
callback: function (value) {
return new Intl.NumberFormat('pl-PL', {
style: 'currency',
currency: 'PLN',
notation: value >= 1000000 ?
'compact' : 'standard',
maximumFractionDigits: 0
}).format(value);
},
// Dynamic step size
stepSize: (() => {
if (maxValue > 100000) return
Math.ceil(maxValue / 100000) * 20000;
if (maxValue > 10000) return
Math.ceil(maxValue / 10000) * 2000;
if (maxValue > 1000) return
Math.ceil(maxValue / 1000) * 200;
return Math.ceil(maxValue / 100) * 20;
})()
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function (context) {
return context.dataset.label + ': ' +
new Intl.NumberFormat('pl-PL', {
style: 'currency',
currency: 'PLN'
}).format(context.parsed.y);
}
}
},
legend: {
position: 'top'
}
}
}
});
}

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 = [];

for (let i = 5; i >= 0; i--) {


const date = new Date();
date.setMonth(date.getMonth() - i);
const monthKey = `${date.getFullYear()}-$
{String(date.getMonth() + 1).padStart(2, '0')}`;
last6Months.push(date.toLocaleDateString('pl-PL', { month:
'short', year: 'numeric' }));

const income = this.dataManager.transactions


.filter(t => t.type === 'income' &&
t.date.startsWith(monthKey))
.reduce((sum, t) => sum + t.amount, 0);
const expenses = this.dataManager.transactions
.filter(t => t.type === 'expense' &&
t.date.startsWith(monthKey))
.reduce((sum, t) => sum + t.amount, 0);

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!');
}
}
}

// Initialize the application


let app;
document.addEventListener('DOMContentLoaded', () => {
app = new FinanceApp();
});

// Async data loading simulation


async function loadExternalData() {
try {
// Simulate fetching exchange rates
const response = await
fetch('https://api.exchangerate-api.com/v4/latest/PLN');
if (response.ok) {
const data = await response.json();
console.log('Exchange rates loaded:', data.rates);
// You could use this data to show currency conversion
}
} catch (error) {
console.log('Could not load external data:', error);
// Fallback to local data
}
}

// Load external data when page loads


loadExternalData();
</script>

<!-- Additional CSS for badges and small buttons -->


<style>
.badge {
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: bold;
}

.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}

.btn-sm i {
font-size: 0.75rem;
}

/* Additional responsive improvements */


@media (max-width: 576px) {
.table {
font-size: 0.875rem;
}

.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);
}

/* Enhanced form validation */


.form-control:invalid {
border-color: var(--danger-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>

You might also like