Zadanie 12b: Domowy Budzet Wpf – rejestr wydatków z filtrem miesięcznym, walidacją i zapisem do pliku


1. Cel aplikacji

Napisz aplikację WPF, która pozwala dodawać wydatki (nazwa, kategoria, kwota, data), wyświetlać je na liście, filtrować po roku i miesiącu oraz obliczać sumę wydatków dla wybranego miesiąca. Dane zapisz/odczytuj z pliku JSON w katalogu danych użytkownika.

2. Wymagania funkcjonalne

  1. Formularz „Nowy wydatek”:
    Nazwa (tekst), Kategoria (lista: Jedzenie, Transport, Rachunki, Rozrywka, Zdrowie, Inne), Kwota (liczba > 0), Data (DatePicker), przycisk Dodaj.
  2. Lista (DataGrid) z kolumnami: Nazwa, Kategoria, Kwota, Data.
  3. Filtry: pola Rok i Miesiąc (1–12). Zmiana filtrów natychmiast odświeża widok listy i Sumę miesiąca.
  4. Suma miesiąca wyświetlana obok filtrów w formacie 0.00 PLN.
  5. Akcje: Zapisz (utrwal dane do JSON), Wczytaj (odczytaj JSON), Usuń zaznaczony.
  6. Walidacja pól formularza „na żywo” (czerwone obramowanie w WPF):
    Nazwa – wymagana, max 50 znaków; Kategoria – wymagana; Kwota – > 0; Data – wymagana.
    Dodaj aktywne tylko, gdy wszystkie pola są poprawne.

3. Wymagania techniczne i nazewnictwo (trzymaj się tej struktury)

DomowyBudzetWpf
├─ MainWindow.xaml
├─ MainWindow.xaml.cs
├─ Models
│  └─ dane.cs              // model danych Wydatek (+ walidacja)
├─ Walidacja
│  └─ WalidacjaBudzet.cs   // helper walidacji + RelayCommand (komendy)
└─ Logika
   └─ LogikaBudzet.cs      // logika/binding/filtr/suma/zapis-odczyt

Nowe elementy i co robią:

  • IDataErrorInfo – interfejs, z którego WPF pobiera komunikaty walidacji; dzięki temu pojawia się czerwone obramowanie.
  • RelayCommand – prosta klasa komend; spina przyciski z metodami w logice.
  • ICollectionView.Filter – filtruje widok danych bez zmiany samej kolekcji.

4. Interfejs – opis rozmieszczenia

  • Góra: GroupBox „Nowy wydatek” z czterema polami i przyciskiem Dodaj po prawej.
  • Poniżej: pasek filtrów (Rok, Miesiąc), etykieta „Suma miesiąca: … PLN”, przyciski Zapisz i Wczytaj.
  • Środek: DataGrid z kolumnami Nazwa, Kategoria, Kwota, Data.
  • Dół: przycisk Usuń zaznaczony po prawej.

Spis kontrolek i paneli (UI)

Okno i siatka główna

  • Window – główne okno aplikacji.
  • Grid (główna) – układ strony, 4 wiersze: Auto, Auto, *, Auto i 2 kolumny: *, *.
    Użyte do rozmieszczenia: GroupBox (formularz), pasek filtrów, DataGrid, pasek akcji.

Sekcja „Nowy wydatek”

  • GroupBox „Nowy wydatek” – obramowanie i tytuł sekcji.
  • Grid (wewnątrz GroupBoxa) – 2 wiersze (Auto) i 4 kolumny (2*, 2*, *, *) dla równych bloków pól.
  • StackPanel ×4 – każdy dla jednego pola z etykietą:
    • TextBlock – etykiety: „Nazwa”, „Kategoria”, „Kwota (PLN)”, „Data”.
    • TextBox x:Name="tbNazwa" – binding Text="{Binding Nowy.Nazwa}", walidacja przez IDataErrorInfo, ToolTip z (Validation.Errors).
    • ComboBox x:Name="cbKategoria"ItemsSource="{Binding Kategorie}", SelectedItem="{Binding Nowy.Kategoria}".
    • TextBox x:Name="tbKwota"Text="{Binding Nowy.Kwota}" (walidacja > 0).
    • DatePicker x:Name="dpData"SelectedDate="{Binding Nowy.Data}".
  • Button x:Name="btnDodaj"Command="{Binding DodajCmd}", aktywność sterowana przez CanExecute (u nas podbijane przez Nowy.PropertyChanged).

Pasek filtrów i sumy

  • StackPanel (poziomy) – zawiera:
    • TextBlock „Rok”, TextBox x:Name="tbRok"Text="{Binding Rok}".
    • TextBlock „Miesiąc (1-12)”, TextBox x:Name="tbMiesiac"Text="{Binding Miesiac}".
    • TextBlock „Suma miesiąca:”, TextBlock x:Name="lblSuma"Text="{Binding SumaMiesiaca, StringFormat={}{0:F2} PLN}".
    • Button x:Name="btnZapisz"Command="{Binding ZapiszCmd}".
    • Button x:Name="btnWczytaj"Command="{Binding WczytajCmd}".

Lista wydatków

  • DataGrid x:Name="dgWydatki"ItemsSource="{Binding WidokWydatkow}", SelectedItem="{Binding Zaznaczony}", kolumny:
    • DataGridTextColumn Header="Nazwa"Binding="{Binding Nazwa}".
    • DataGridTextColumn Header="Kategoria"Binding="{Binding Kategoria}".
    • DataGridTextColumn Header="Kwota"Binding="{Binding Kwota, StringFormat={}{0:F2}}".
    • DataGridTextColumn Header="Data"Binding="{Binding Data, StringFormat={}{0:yyyy-MM-dd}}".

Pasek akcji dolny

  • StackPanel (poziomy, prawa krawędź)
    • Button x:Name="btnUsun"Content="Usuń zaznaczony", Command="{Binding UsunCmd}".

Dodatkowe elementy „niewizualne”, ale istotne dla UI

  • ToolTip na polach tekstowych – pokazuje pierwszy błąd walidacji z (Validation.Errors)[0].ErrorContent.
  • ICollectionView z Filter – to nie kontrolka, ale mechanizm, który steruje tym, co widzi DataGrid (filtrowanie po Rok/Miesiac).
  • MessageBox – potwierdzenia/komunikaty przy zapisie i odczycie (środowisko WPF).

5. Algorytmy (pseudokod)

5.1. Dodawanie wydatku

CanDodaj():
  zwróć TRUE tylko, jeśli:
    Nazwa != "" i długość <= 50
    Kategoria wybrana
    Kwota > 0
    Data ustawiona

Dodaj():
  jeśli CanDodaj():
    kopia = Wydatek(Nazwa, Kategoria, Kwota, Data)
    Wydatki.Add(kopia)
    Nowy = Wydatek(Kategoria = kopia.Kategoria, Data = dzisiaj)   // reset formularza
    WidokWydatkow.Refresh()
    SumaMiesiaca = przelicz()

5.2. Filtrowanie i suma

FiltrMiesiaca(wydatek):
  zwróć (wydatek.Data.Year == Rok) i (wydatek.Data.Month == Miesiac)

przelicz():
  suma = 0
  dla każdego w w Wydatki:
    jeśli w.Data.Year == Rok i w.Data.Month == Miesiac:
      suma += w.Kwota
  zwróć suma

5.3. Usuwanie i zapis/odczyt

Usuń():
  jeśli Zaznaczony != null:
    Wydatki.Remove(Zaznaczony)
    WidokWydatkow.Refresh()
    SumaMiesiaca = przelicz()

Zapisz():
  ścieżka = AppData\DomowyBudzetWpf\wydatki.json
  utwórz katalog jeśli nie istnieje
  json = serializuj(Wydatki)
  zapisz json do pliku

Wczytaj():
  jeśli plik istnieje:
    lista = deserializuj(json)
    Wydatki.Clear(); Wydatki.AddRange(lista)
    WidokWydatkow.Refresh()
    SumaMiesiaca = przelicz()

6. Kroki wykonania (skrót dla ucznia)

  1. Utwórz WPF App „DomowyBudzetWpf” (.NET 6/8).
  2. Dodaj foldery i pliki zgodnie z pkt 3.
  3. W MainWindow.xaml zbuduj układ.
  4. W dane.cs zdefiniuj model Wydatek z atrybutami walidacyjnymi i IDataErrorInfo.
  5. W WalidacjaBudzet.cs dodaj helper walidacji i RelayCommand.
  6. W LogikaBudzet.cs zaimplementuj: ObservableCollection<Wydatek>, formularz Nowy, filtry Rok, Miesiac, SumaMiesiaca, ICollectionView.Filter, komendy Dodaj/Usuń/Zapisz/Wczytaj, zapis/odczyt JSON, oraz subskrypcję Nowy.PropertyChanged (żeby Dodaj się uaktywniał).
  7. W MainWindow.xaml.cs ustaw DataContext = new LogikaBudzet();
  8. Uruchom i przetestuj.

7. Testy (co sprawdza egzaminator)

  • Walidacja blokuje Dodaj przy błędnych/ pustych danych.
  • Po zmianie Rok/Miesiąc lista i Suma zmieniają się natychmiast.
  • Dodanie poprawnego wpisu pojawia się w liście; suma rośnie.
  • „Usuń zaznaczony” usuwa wpis i poprawnie przelicza sumę.
  • „Zapisz” tworzy plik JSON, „Wczytaj” odtwarza stan.

8. Kryteria oceniania (30 pkt)

  • Model + walidacja DataAnnotations + IDataErrorInfo – 6 pkt
  • Poprawne bindingi (formularz, lista, suma, filtry) – 8 pkt
  • Komendy i logika Dodaj/Usuń – 6 pkt
  • Filtr i natychmiastowe odświeżanie widoku + poprawna suma – 6 pkt
  • Zapis/odczyt JSON + poprawna ścieżka AppData – 4 pkt
    Błędy krytyczne: brak walidacji, nieaktywujący się Dodaj, błędna suma, niedziałający filtr, uszkodzony zapis/odczyt.

9. Co oddać

  • Projekt Visual Studio w folderze DomowyBudzetWpf (spakowany .zip).
  • Plik wydatki.json (jeśli wygenerowany) w AppData – nie jest wymagany w archiwum.
  • Krótkie „readme.txt” (opcjonalnie): jak uruchomić, gdzie zapisuje dane.