0% found this document useful (0 votes)
49 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)
49 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

<!

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