Czego się nauczysz?
- Walidacji danych liczbowych (kwoty)
- Pracy z datami i filtrowania po miesiącach
- Kategoryzacji danych (enum kategorii)
- Obliczania sum i procentów
- Tworzenia raportów finansowych
This content is not available in your language yet.
Stworzysz Rejestr wydatków miesięcznych - aplikację do śledzenia i kategoryzowania wydatków. System pozwala na przeglądanie wydatków w wybranym miesiącu, generowanie raportów według kategorii i analizę struktury kosztów.
Czego się nauczysz?
W prawdziwej pracy...
Rejestry wydatków są podstawą aplikacji finansowych - od osobistych budżetów (Mint, YNAB) po systemy księgowe firm. Umiejętność projektowania systemów z ewidencją transakcji, kategoriami i raportami jest fundamentem dla każdego programisty aplikacji finansowych i e-commerce.
Formularz dodawania wydatku Użytkownik podaje kwotę, wybiera kategorię z listy, opcjonalnie podaje datę i opis wydatku.
Walidacja danych System sprawdza poprawność wprowadzonych danych - czy kwota jest dodatnia, czy kategoria jest z dozwolonej listy.
Lista wydatków Wszystkie wydatki są wyświetlane w formie listy z podsumowaniem sumy.
Podsumowanie miesięczne System oblicza sumę wydatków w bieżącym miesiącu i może pokazywać różne miesiące.
Przykładowa struktura pliku JSON:
{ "expenses": [ { "id": 1, "amount": 45.50, "category": "jedzenie", "date": "2026-02-10", "description": "Zakupy w Biedronce", "created_at": "2026-02-10 18:30:00" }, { "id": 2, "amount": 150.00, "category": "transport", "date": "2026-02-11", "description": "Bilet miesięczny", "created_at": "2026-02-11 08:00:00" }, { "id": 3, "amount": 89.99, "category": "rozrywka", "date": "2026-02-12", "description": "Netflix + Spotify", "created_at": "2026-02-12 20:00:00" }, { "id": 4, "amount": 250.00, "category": "rachunki", "date": "2026-02-15", "description": "Prąd", "created_at": "2026-02-15 10:30:00" } ]}| Kategoria | Ikona | Kolor |
|---|---|---|
| jedzenie | 🍕 | #e74c3c |
| transport | 🚌 | #3498db |
| rozrywka | 🎬 | #9b59b6 |
| rachunki | 💡 | #f39c12 |
| zakupy | 🛒 | #2ecc71 |
| zdrowie | 💊 | #1abc9c |
| inne | 📦 | #95a5a6 |
Wymagane funkcje:
Przykładowy scenariusz:
Ocena: 3.0Użytkownik wpisuje kwotę 45.50 zł, wybiera kategorię “jedzenie”, dodaje opis “Zakupy”. Po zapisie widzi wydatek na liście. Na dole: “Suma: 535,49 zł”.
Wszystko z wariantu A, plus:
Przykładowy scenariusz:
Ocena: 4.0-5.0Użytkownik wybiera miesiąc “Luty 2026” i widzi tylko wydatki z tego miesiąca. Może też wybrać kategorię “jedzenie” i zobaczyć ile wydał na jedzenie. Sortuje po kwocie malejąco, żeby zobaczyć największe wydatki.
Wszystko z wariantu B, plus:
Przykładowy scenariusz:
Ocena: 5.0-6.0Użytkownik widzi wykres kołowy: “Jedzenie: 35%, Rachunki: 25%, Transport: 20%”. Porównanie: “vs styczeń: +150 zł (+12%)”. Ustawił budżet 2000 zł - system ostrzega gdy wydał już 1800 zł (90%).
Walidacja danych wejściowych:
$amount = filter_var($_POST['amount'] ?? '', FILTER_VALIDATE_FLOAT);$category = $_POST['category'] ?? '';$date = $_POST['date'] ?? date('Y-m-d');
if ($amount === false || $amount <= 0) { $errors[] = "Kwota musi być liczbą dodatnią";}
if ($amount > 100000) { $errors[] = "Kwota wydaje się zbyt duża";}
$validCategories = ['jedzenie', 'transport', 'rozrywka', 'rachunki', 'zakupy', 'zdrowie', 'inne'];if (!in_array($category, $validCategories)) { $errors[] = "Wybierz poprawną kategorię";}
// Walidacja daty (nie z przyszłości)if ($date > date('Y-m-d')) { $errors[] = "Data nie może być z przyszłości";}Kategorie z kolorami i ikonami:
function getCategories(): array { return [ 'jedzenie' => ['label' => 'Jedzenie', 'icon' => '🍕', 'color' => '#e74c3c'], 'transport' => ['label' => 'Transport', 'icon' => '🚌', 'color' => '#3498db'], 'rozrywka' => ['label' => 'Rozrywka', 'icon' => '🎬', 'color' => '#9b59b6'], 'rachunki' => ['label' => 'Rachunki', 'icon' => '💡', 'color' => '#f39c12'], 'zakupy' => ['label' => 'Zakupy', 'icon' => '🛒', 'color' => '#2ecc71'], 'zdrowie' => ['label' => 'Zdrowie', 'icon' => '💊', 'color' => '#1abc9c'], 'inne' => ['label' => 'Inne', 'icon' => '📦', 'color' => '#95a5a6'], ];}
function getCategoryInfo(string $category): array { $categories = getCategories(); return $categories[$category] ?? $categories['inne'];}Filtrowanie po miesiącu:
function filterByMonth(array $expenses, int $year, int $month): array { return array_filter($expenses, function($expense) use ($year, $month) { $expenseDate = strtotime($expense['date']); return date('Y', $expenseDate) == $year && date('n', $expenseDate) == $month; });}
function getAvailableMonths(array $expenses): array { $months = [];
foreach ($expenses as $expense) { $yearMonth = date('Y-m', strtotime($expense['date'])); if (!in_array($yearMonth, $months)) { $months[] = $yearMonth; } }
rsort($months); // Najnowsze pierwsze return $months;}
function formatMonth(string $yearMonth): string { $monthNames = [ '01' => 'Styczeń', '02' => 'Luty', '03' => 'Marzec', '04' => 'Kwiecień', '05' => 'Maj', '06' => 'Czerwiec', '07' => 'Lipiec', '08' => 'Sierpień', '09' => 'Wrzesień', '10' => 'Październik', '11' => 'Listopad', '12' => 'Grudzień', ];
list($year, $month) = explode('-', $yearMonth); return $monthNames[$month] . ' ' . $year;}Raport według kategorii:
function getCategoryReport(array $expenses): array { $report = []; $total = 0;
foreach ($expenses as $expense) { $category = $expense['category']; $amount = $expense['amount'];
if (!isset($report[$category])) { $report[$category] = ['amount' => 0, 'count' => 0]; }
$report[$category]['amount'] += $amount; $report[$category]['count']++; $total += $amount; }
// Dodaj procenty foreach ($report as $category => &$data) { $data['percentage'] = $total > 0 ? round(($data['amount'] / $total) * 100, 1) : 0; }
// Sortuj po kwocie malejąco uasort($report, fn($a, $b) => $b['amount'] <=> $a['amount']);
return [ 'categories' => $report, 'total' => round($total, 2), ];}Porównanie miesięcy:
function compareMonths(array $expenses, int $year, int $month): array { $currentMonth = filterByMonth($expenses, $year, $month); $currentTotal = array_sum(array_column($currentMonth, 'amount'));
// Poprzedni miesiąc $prevMonth = $month - 1; $prevYear = $year; if ($prevMonth < 1) { $prevMonth = 12; $prevYear--; }
$previousMonth = filterByMonth($expenses, $prevYear, $prevMonth); $previousTotal = array_sum(array_column($previousMonth, 'amount'));
$difference = $currentTotal - $previousTotal; $percentChange = $previousTotal > 0 ? round(($difference / $previousTotal) * 100, 1) : 0;
return [ 'current' => round($currentTotal, 2), 'previous' => round($previousTotal, 2), 'difference' => round($difference, 2), 'percentage' => $percentChange, 'trend' => $difference > 0 ? 'up' : ($difference < 0 ? 'down' : 'stable'), ];}Sprawdzanie budżetu (wariant C):
function checkBudget(array $expenses, float $budgetLimit, int $year, int $month): array { $monthExpenses = filterByMonth($expenses, $year, $month); $spent = array_sum(array_column($monthExpenses, 'amount')); $remaining = $budgetLimit - $spent; $percentage = ($budgetLimit > 0) ? round(($spent / $budgetLimit) * 100, 1) : 0;
$status = 'ok'; $message = '';
if ($percentage >= 100) { $status = 'exceeded'; $message = 'Przekroczono budżet o ' . abs(round($remaining, 2)) . ' zł'; } elseif ($percentage >= 80) { $status = 'warning'; $message = 'Wykorzystano ' . $percentage . '% budżetu'; }
return [ 'budget' => $budgetLimit, 'spent' => round($spent, 2), 'remaining' => round($remaining, 2), 'percentage' => $percentage, 'status' => $status, 'message' => $message, ];}Formatowanie kwot:
function formatAmount(float $amount): string { return number_format($amount, 2, ',', ' ') . ' zł';}Wykorzystaj lekcje!
Cotygodniowe spotkania podczas lekcji to idealny moment, by:
Pracuj iteracyjnie - lepiej mieć działający wariant A niż niedokończony C!