Czego się nauczysz?
- Walidacji kwot i formatów liczbowych
- Pracy z kategoriami i typami transakcji
- Agregowania danych (sumy, średnie)
- Generowania raportów i statystyk
- Filtrowania po dacie i kategorii
This content is not available in your language yet.
Stworzysz Budżet domowy - aplikację do rejestrowania wydatków i przychodów z podziałem na kategorie. System pozwala śledzić finanse, generować podsumowania i tworzyć raporty miesięczne.
Czego się nauczysz?
W prawdziwej pracy...
Aplikacje finansowe są podstawą zarządzania w każdej firmie i gospodarstwie domowym - od rejestrów wydatków, przez systemy księgowe, po aplikacje bankowe. Umiejętność projektowania systemów z walidacją kwot, kategoriami i raportowaniem finansowym jest fundamentem dla każdego programisty aplikacji biznesowych.
Formularz dodawania wpisu Użytkownik podaje typ (wydatek/przychód), kwotę, kategorię, opis i datę.
Walidacja danych System sprawdza poprawność wprowadzonych danych - czy kwota jest dodatnia, czy kategoria jest z dozwolonej listy.
Lista transakcji Wszystkie wpisy są wyświetlane z informacją o typie, kwocie i kategorii.
Podsumowania System pokazuje sumy wydatków i przychodów oraz bilans.
Przykładowa struktura pliku JSON:
{ "transactions": [ { "id": 1, "type": "expense", "amount": 150.50, "category": "jedzenie", "description": "Zakupy spożywcze w Biedronce", "date": "2026-02-10", "created_at": "2026-02-10 18:30:00" }, { "id": 2, "type": "income", "amount": 5000.00, "category": "wynagrodzenie", "description": "Pensja za luty", "date": "2026-02-01", "created_at": "2026-02-01 10:00:00" }, { "id": 3, "type": "expense", "amount": 89.99, "category": "rozrywka", "description": "Netflix + Spotify", "date": "2026-02-05", "created_at": "2026-02-05 12:15:00" }, { "id": 4, "type": "expense", "amount": 1200.00, "category": "mieszkanie", "description": "Czynsz za luty", "date": "2026-02-02", "created_at": "2026-02-02 09:00:00" } ]}Wymagane funkcje:
Przykładowy scenariusz:
Ocena: 3.0Użytkownik wybiera “Wydatek”, wpisuje kwotę 150.50 zł, kategorię “Jedzenie”, opis “Zakupy” i datę. Po zapisie widzi transakcję na liście z czerwoną ikonką wydatku.
Wszystko z wariantu A, plus:
htmlspecialchars() przy wyświetlaniuPrzykładowy scenariusz:
Ocena: 4.0-5.0Użytkownik widzi bilans: “Przychody: 5000 zł, Wydatki: 1440 zł, Saldo: +3560 zł”. Klika “Pokaż tylko wydatki” i widzi tylko transakcje typu expense. Podsumowanie pokazuje: “Jedzenie: 150 zł, Mieszkanie: 1200 zł”.
Wszystko z wariantu B, plus:
Przykładowy scenariusz:
Ocena: 5.0-6.0Użytkownik filtruje transakcje: “Luty 2026” i widzi wszystkie wpisy z tego miesiąca. Klika “Generuj raport” i pobiera HTML z podsumowaniem: wydatki per kategoria, przychody, bilans, wykres struktury wydatków.
Walidacja transakcji:
$type = $_POST['type'] ?? '';$amount = $_POST['amount'] ?? '';$category = $_POST['category'] ?? '';$description = trim($_POST['description'] ?? '');$date = $_POST['date'] ?? '';
$validTypes = ['expense', 'income'];$validCategories = [ 'expense' => ['jedzenie', 'mieszkanie', 'transport', 'rozrywka', 'zdrowie', 'inne'], 'income' => ['wynagrodzenie', 'premia', 'freelance', 'inne'],];
if (!in_array($type, $validTypes)) { $errors[] = "Wybierz typ transakcji";}
if (!is_numeric($amount) || $amount <= 0) { $errors[] = "Kwota musi być liczbą większą od zera";}
if ($amount > 1000000) { $errors[] = "Kwota nie może przekraczać 1 000 000 zł";}
if (!in_array($category, $validCategories[$type] ?? [])) { $errors[] = "Wybierz poprawną kategorię";}
if (empty($date)) { $errors[] = "Data jest wymagana";}Kategorie z ikonami i kolorami:
function getExpenseCategories(): array { return [ 'jedzenie' => ['label' => 'Jedzenie', 'icon' => '🍕', 'color' => '#e74c3c'], 'mieszkanie' => ['label' => 'Mieszkanie', 'icon' => '🏠', 'color' => '#3498db'], 'transport' => ['label' => 'Transport', 'icon' => '🚗', 'color' => '#9b59b6'], 'rozrywka' => ['label' => 'Rozrywka', 'icon' => '🎮', 'color' => '#f39c12'], 'zdrowie' => ['label' => 'Zdrowie', 'icon' => '💊', 'color' => '#27ae60'], 'inne' => ['label' => 'Inne', 'icon' => '📦', 'color' => '#95a5a6'], ];}
function getIncomeCategories(): array { return [ 'wynagrodzenie' => ['label' => 'Wynagrodzenie', 'icon' => '💼', 'color' => '#27ae60'], 'premia' => ['label' => 'Premia', 'icon' => '🎁', 'color' => '#2ecc71'], 'freelance' => ['label' => 'Freelance', 'icon' => '💻', 'color' => '#1abc9c'], 'inne' => ['label' => 'Inne', 'icon' => '💰', 'color' => '#16a085'], ];}Dodawanie transakcji:
function addTransaction(array &$transactions, array $data): int { $newId = empty($transactions) ? 1 : max(array_column($transactions, 'id')) + 1;
$newTransaction = [ 'id' => $newId, 'type' => $data['type'], 'amount' => (float) $data['amount'], 'category' => $data['category'], 'description' => $data['description'], 'date' => $data['date'], 'created_at' => date('Y-m-d H:i:s'), ];
$transactions[] = $newTransaction; return $newId;}Obliczanie podsumowań:
function calculateSummary(array $transactions): array { $income = 0; $expense = 0;
foreach ($transactions as $t) { if ($t['type'] === 'income') { $income += $t['amount']; } else { $expense += $t['amount']; } }
return [ 'income' => $income, 'expense' => $expense, 'balance' => $income - $expense, ];}
function calculateByCategory(array $transactions, string $type = 'expense'): array { $filtered = array_filter($transactions, fn($t) => $t['type'] === $type);
$byCategory = []; foreach ($filtered as $t) { $category = $t['category']; if (!isset($byCategory[$category])) { $byCategory[$category] = 0; } $byCategory[$category] += $t['amount']; }
arsort($byCategory); return $byCategory;}Filtrowanie transakcji:
function filterByType(array $transactions, string $type): array { if (empty($type) || $type === 'all') { return $transactions; } return array_filter($transactions, fn($t) => $t['type'] === $type);}
function filterByMonth(array $transactions, string $yearMonth): array { // $yearMonth format: "2026-02" return array_filter($transactions, function($t) use ($yearMonth) { return strpos($t['date'], $yearMonth) === 0; });}
function filterByDateRange(array $transactions, string $from, string $to): array { return array_filter($transactions, function($t) use ($from, $to) { return $t['date'] >= $from && $t['date'] <= $to; });}
function sortByDate(array $transactions, string $order = 'desc'): array { usort($transactions, function($a, $b) use ($order) { $result = strcmp($a['date'], $b['date']); return $order === 'desc' ? -$result : $result; }); return $transactions;}Formatowanie kwot:
function formatAmount(float $amount, string $type = 'expense'): string { $formatted = number_format($amount, 2, ',', ' ') . ' zł';
if ($type === 'income') { return '+' . $formatted; } elseif ($type === 'expense') { return '-' . $formatted; }
return $formatted;}
function formatBalance(float $balance): string { $formatted = number_format(abs($balance), 2, ',', ' ') . ' zł';
if ($balance > 0) { return '<span class="positive">+' . $formatted . '</span>'; } elseif ($balance < 0) { return '<span class="negative">-' . $formatted . '</span>'; }
return '<span class="neutral">' . $formatted . '</span>';}Generowanie raportu miesięcznego:
function generateMonthlyReport(array $transactions, string $yearMonth): string { $filtered = filterByMonth($transactions, $yearMonth); $summary = calculateSummary($filtered); $byCategory = calculateByCategory($filtered, 'expense');
$monthName = date('F Y', strtotime($yearMonth . '-01'));
$html = '<!DOCTYPE html><html><head><meta charset="UTF-8">'; $html .= '<title>Raport budżetowy - ' . $monthName . '</title>'; $html .= '<style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; } .summary { display: flex; gap: 20px; margin-bottom: 30px; } .summary-box { flex: 1; padding: 20px; border-radius: 10px; text-align: center; } .income { background: #d4edda; } .expense { background: #f8d7da; } .balance { background: #cce5ff; } .amount { font-size: 2em; font-weight: bold; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } th, td { padding: 10px; border: 1px solid #ddd; text-align: left; } th { background: #333; color: white; } </style></head><body>';
$html .= '<h1>Raport budżetowy: ' . htmlspecialchars($monthName) . '</h1>';
$html .= '<div class="summary">'; $html .= '<div class="summary-box income"><div>Przychody</div><div class="amount">' . formatAmount($summary['income'], 'income') . '</div></div>'; $html .= '<div class="summary-box expense"><div>Wydatki</div><div class="amount">' . formatAmount($summary['expense'], 'expense') . '</div></div>'; $html .= '<div class="summary-box balance"><div>Bilans</div><div class="amount">' . formatAmount($summary['balance']) . '</div></div>'; $html .= '</div>';
$html .= '<h2>Wydatki per kategoria</h2>'; $html .= '<table><tr><th>Kategoria</th><th>Kwota</th><th>Udział</th></tr>';
$totalExpense = $summary['expense'] ?: 1; foreach ($byCategory as $category => $amount) { $percent = round(($amount / $totalExpense) * 100, 1); $html .= '<tr>'; $html .= '<td>' . htmlspecialchars($category) . '</td>'; $html .= '<td>' . formatAmount($amount) . '</td>'; $html .= '<td>' . $percent . '%</td>'; $html .= '</tr>'; }
$html .= '</table></body></html>'; return $html;}Wykorzystaj lekcje!
Cotygodniowe spotkania podczas lekcji to idealny moment, by:
Pracuj iteracyjnie - lepiej mieć działający wariant A niż niedokończony C!