Files
BIU_System_Rezerwacji/README.md

298 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Reservation Management System
Aplikacja SPA do zarządzania rezerwacjami zbudowana w React 19 + Vite 8. Umożliwia klientom przeglądanie dostępnych terminów, dokonywanie rezerwacji z płatnością depozytu, zarządzanie historią rezerwacji oraz wystawianie opinii. Administratorzy mają dostęp do panelu zarządzania, kalendarza rezerwacji, analityki i eksportu danych.
---
## Uruchomienie
```bash
# Terminal 1 REST API (json-server na porcie 3001)
npm run api
# Terminal 2 Vite dev server (port 5173)
npm run dev
# Testy jednostkowe (Vitest + RTL)
npm test
# Testy e2e (Playwright)
node test_rms.mjs
# Storybook (port 6006)
npm run storybook
```
> **Uwaga:** przed uruchomieniem `test_rms.mjs` oba serwery muszą być aktywne. Test automatycznie przywraca dane seed (r1, r2) do stanu `pending` przed każdym przebiegiem.
---
## Dane testowe
| Rola | E-mail | Hasło |
|------|--------|-------|
| Admin | `admin@reservations.dev` | `Admin1234!` |
| Klient | `anna.kowalski@example.com` | `Client1234!` |
Logowanie zawsze uruchamia **weryfikację 2FA** kod jest wyświetlany bezpośrednio w interfejsie (symulowany e-mail).
---
## Stos technologiczny
| Warstwa | Technologia |
|---------|-------------|
| UI | React 19, Vite 8, SCSS Modules |
| Routing | React Router v7 |
| Zapytania REST | TanStack React Query v5 + Axios |
| Zapytania GraphQL | Apollo Client v4 + SchemaLink (in-memory) |
| Formularze | Formik + Zod (`zod-formik-adapter`) |
| Ikony | Lucide React |
| REST mock | json-server (db.json) |
| Testy jednostkowe | Vitest + React Testing Library |
| Testy e2e | Playwright |
| Dokumentacja | Storybook 8 |
---
## Struktura projektu
```
web_projekt/
├── db.json # Baza danych json-server (users, services, reservations)
├── test_rms.mjs # Testy e2e Playwright (32 testy)
├── src/
│ ├── main.jsx # Punkt wejścia: Apollo Client, QueryClient, renderowanie
│ ├── App.jsx # Router, lazy loading, ErrorBoundary, Suspense, ProtectedRoute
│ ├── index.css # Reset + CSS custom properties (tryb ciemny/jasny)
│ ├── styles/
│ │ ├── _variables.scss # Tokeny projektu: kolory, spacing, typografia, cienie, media queries
│ │ └── main.scss # Globalne style bazowe
│ ├── api/
│ │ ├── reservations.js # CRUD rezerwacji przez Axios (json-server :3001)
│ │ ├── services.js # Pobieranie listy usług
│ │ └── users.js # Pobieranie i aktualizacja użytkowników
│ ├── context/
│ │ ├── AuthContext.jsx # Stan uwierzytelnienia, 2FA, role, sesja w localStorage
│ │ └── ThemeContext.jsx # Przełącznik jasny/ciemny motyw (data-theme na <html>)
│ ├── hocs/
│ │ └── withRole.jsx # HOC: renderuje komponent tylko dla dozwolonych ról
│ ├── hooks/
│ │ ├── useCreateReservation.js # useMutation: POST /reservations
│ │ ├── useDeleteReservation.js # useMutation: DELETE /reservations/:id
│ │ ├── useReservations.js # useQuery: GET /reservations (wszystkie)
│ │ ├── useServices.js # useQuery: GET /services
│ │ ├── useSlotReservations.js # useQuery: GET /reservations?serviceId&date
│ │ ├── useUpdateReservation.js # useMutation: PATCH /reservations/:id
│ │ └── useUserReservations.js # useQuery: GET /reservations?userId
│ ├── pages/
│ │ ├── LoginPage.jsx # Formularz logowania (Formik+Zod) + ekran 2FA z podglądem kodu
│ │ ├── LoginPage.module.scss
│ │ ├── RegisterPage.jsx # Rejestracja z walidacją Zod i weryfikacją e-mail
│ │ ├── RegisterPage.module.scss
│ │ ├── DashboardPage.jsx # Panel klienta: AvailabilitySearch, SlotFinder, BookingWizard, MyReservations, ProfileEditModal
│ │ ├── DashboardPage.module.scss
│ │ ├── ReservationsPage.jsx # Historia rezerwacji z filtrami, anulowaniem, RescheduleModal, ReviewsSection, Google Calendar, re-book
│ │ ├── ReservationsPage.module.scss
│ │ ├── AdminPage.jsx # Panel admina: tabela rezerwacji, UserManagement, AnalyticsDashboard, AdminCalendar, blokowanie terminów (useTransition)
│ │ └── AdminPage.module.scss
│ ├── components/
│ │ ├── AvailabilitySearch.jsx # Wyszukiwanie wolnych slotów z synchronizacją URL (useSearchParams)
│ │ ├── AvailabilitySearch.module.scss
│ │ ├── BookingWizard.jsx # 4-krokowy kreator rezerwacji (Usługa → Data → Wymagania → Podsumowanie)
│ │ ├── BookingWizard.module.scss
│ │ ├── BookingForm.jsx # Uproszczony formularz rezerwacji (react-hook-form)
│ │ ├── BookingForm.module.scss
│ │ ├── SlotFinder.jsx # Inteligentny finder slotów z punktacją (scoreSlot, findSlots)
│ │ ├── SlotFinder.module.scss
│ │ ├── PaymentModal.jsx # Modal płatności: Karta / Google Pay / Przelew bankowy (Portal)
│ │ ├── PaymentModal.module.scss
│ │ ├── RescheduleModal.jsx # Modal zmiany terminu rezerwacji (Portal)
│ │ ├── RescheduleModal.module.scss
│ │ ├── ProfileEditModal.jsx # Modal edycji profilu i preferencji powiadomień (Portal, Formik+Zod)
│ │ ├── ProfileEditModal.module.scss
│ │ ├── ReviewsSection.jsx # Sekcja opinii (GraphQL: useQuery + useMutation przez Apollo)
│ │ ├── ReviewsSection.module.scss
│ │ ├── ProfileView.jsx # Widok profilu (GraphQL: userProfile query)
│ │ ├── ProfileView.module.scss
│ │ ├── MyReservations.jsx # Sidebar z aktywnymi rezerwacjami użytkownika
│ │ ├── MyReservations.module.scss
│ │ ├── AdminCalendar.jsx # Widok kalendarza dla admina (miesiąc, kliknięcie dnia, panel boczny)
│ │ ├── AdminCalendar.module.scss
│ │ ├── AnalyticsDashboard.jsx # Statystyki rezerwacji z eksportem CSV (DataRenderer, useMemo)
│ │ ├── AnalyticsDashboard.module.scss
│ │ ├── UserManagement.jsx # Zarządzanie użytkownikami przez admina (tabela, dezaktywacja, reset hasła)
│ │ ├── UserManagement.module.scss
│ │ ├── Modal.jsx # Bazowy komponent modala (Portal, useCallback, Escape key, aria)
│ │ ├── Modal.module.scss
│ │ ├── StatusBadge.jsx # Odznaka statusu rezerwacji (warianty kolorów, rozmiary, aria-label)
│ │ ├── StatusBadge.module.scss
│ │ ├── StatusBadge.test.jsx # Testy RTL dla StatusBadge
│ │ ├── ThemeToggle.jsx # Przycisk przełączania motywu (jasny/ciemny)
│ │ ├── ThemeToggle.module.scss
│ │ ├── DataRenderer.jsx # Render-prop: opakowuje useQuery, przekazuje dane przez children()
│ │ ├── ErrorBoundary.jsx # Class component: łapie błędy runtime, wyświetla fallback UI
│ │ ├── ErrorBoundary.module.scss
│ │ ├── LoadingSpinner.jsx # Animowany spinner ładowania
│ │ ├── LoadingSpinner.module.scss
│ │ └── ProtectedRoute.jsx # Guard routingu: sprawdza rolę i przekierowuje
│ ├── stories/
│ │ ├── StatusBadge.stories.jsx # Storybook: wszystkie warianty odznaki statusu
│ │ ├── Modal.stories.jsx # Storybook: modal informacyjny i destrukcyjny
│ │ └── ThemeToggle.stories.jsx # Storybook: przełącznik motywu
│ └── tests/
│ ├── LoginPage.test.jsx # RTL: formularz logowania, błędy walidacji
│ ├── BookingForm.test.jsx # RTL: formularz rezerwacji, react-hook-form
│ ├── ProtectedRoute.test.jsx # RTL: ochrona tras, przekierowania
│ └── StatusBadge.test.jsx # RTL: warianty statusu
└── public/
└── index.html # Zawiera <div id="root"> i <div id="portal-root">
```
---
## Architektura danych
### REST (json-server :3001)
- `GET/POST /reservations` rezerwacje
- `GET /services` usługi
- `GET/PATCH /users` użytkownicy
- Filtrowanie przez query params: `?userId=`, `?serviceId=`, `?date=`
### GraphQL (Apollo Client in-memory, bez zewnętrznego serwera)
Schema i resolvery zdefiniowane w `main.jsx`, wykonywane lokalnie przez `SchemaLink`:
```graphql
type Query {
userProfile(id: ID!): UserProfile
serviceReviews(serviceId: String!): [Review!]!
userReviews(userId: String!): [Review!]!
}
type Mutation {
submitReview(reservationId: String!, serviceId: String!, ...): Review!
}
```
---
## Lista wymagań i ich realizacja
### Wymagania funkcjonalne (11)
| # | Kryterium | Realizacja | Status |
|---|-----------|-----------|--------|
| F1 | **System uwierzytelniania i autoryzacji** | `AuthContext.jsx` logowanie e-mail + 2FA (kod wyświetlany w UI); role `client`/`admin`; sesja w `localStorage`; rejestracja z weryfikacją e-mail (`RegisterPage.jsx`); `ProtectedRoute` blokuje dostęp wg roli | [OK] |
| F2 | **Zarządzanie profilami użytkowników** | `ProfileEditModal.jsx` edycja danych (Formik+Zod, PATCH `/users`), 5 preferencji powiadomień (toggle switches: potwierdzenie, anulowanie, przypomnienie 24h, zmiana terminu, promocje); `ProfileView.jsx` dane z GraphQL; historia w `ReservationsPage.jsx` | [OK] |
| F3 | **Wyszukiwanie i przeglądanie dostępności** | `AvailabilitySearch.jsx` filtracja po dacie i usłudze, siatka slotów, URL sync (`useSearchParams`); `SlotFinder.jsx` ranking slotów w zakresie dat z preferencjami (rano/południe/wieczór), pasek score | [OK] |
| F4 | **Proces rezerwacji** | `BookingWizard.jsx` 4 kroki: wybór usługi, data+czas (blokada zajętych slotów), wymagania specjalne, podsumowanie z ceną; `PaymentModal.jsx` 3 metody płatności, potwierdzenie z paragonem | [OK] |
| F5 | **System płatności online** | `PaymentModal.jsx` karta kredytowa (Formik, walidacja), Google Pay (symulacja), przelew bankowy (IBAN/BIC, numer referencyjny); depozyt 50% ceny; `transactionId` zapisywany w rezerwacji | [OK] |
| F6 | **Zarządzanie rezerwacjami przez użytkowników** | `ReservationsPage.jsx` filtry (wszystkie/nadchodzące/minione/anulowane), anulowanie z potwierdzeniem, `RescheduleModal.jsx` (zmiana terminu), "Book again" (re-book), Google Calendar link | [OK] |
| F7 | **Panel zarządzania rezerwacjami (admin)** | `AdminPage.jsx` tabela rezerwacji (Confirm/Cancel/Delete), `AdminCalendar.jsx` (miesiąc, panel dnia), blokowanie i zwalnianie terminów z datepickerem i listą | [OK] |
| F8 | **Zarządzanie użytkownikami (admin)** | `UserManagement.jsx` tabela użytkowników, dezaktywacja konta (toggle), symulacja resetu hasła; dostęp chroniony przez HOC `withRole(['admin'])` | [OK] |
| F9 | **Analityka i raportowanie** | `AnalyticsDashboard.jsx` statystyki (łączna liczba, potwierdzone, anulowane, przychód z depozytów, średnia ocena); wykresy słupkowe CSS; eksport CSV z nagłówkami | [OK] |
| F10 | **Integracja z zewnętrznymi systemami** | Google Calendar: link `eventedit` generowany dynamicznie z datą/godziną/tytułem dla każdej nadchodzącej rezerwacji; pełne REST API dostępne dla zewnętrznych integracji | [OK] |
| F11 | **System opinii i ocen** | `ReviewsSection.jsx` ocena gwiazdkowa (15) + komentarz, zapis przez GraphQL mutation `submitReview`; pobieranie przez `serviceReviews` query; widoczne po rozwinięciu minionej rezerwacji | [OK] |
### Wymagania wizualne BIU ZAO (6)
| # | Kryterium | Realizacja | Status |
|---|-----------|-----------|--------|
| V1 | **Spójny i nowoczesny interfejs** | Jeden design system: tokeny w `_variables.scss` (paleta primary/accent/gray, spacing 4px scale, typografia, cienie), spójne BEM, Lucide icons, karty z `border-radius`, modale z overlay | [OK] |
| V2 | **Pełna responsywność** | 12 breakpointów w SCSS (`$bp-xs` 480px`$bp-2xl` 1536px); grid w DashboardPage stackuje kolumny, tabela admina scrolluje poziomo, wizard adaptuje układ pól | [OK] |
| V3 | **Animacje i efekty przejścia** | CSS `transition` na przyciskach/kartach/modalach; `@keyframes` pulse na wyróżnionej karcie rezerwacji; spinner ładowania CSS; hover lift (`box-shadow` + `transform`) | [OK] |
| V4 | **Wizualny feedback dla interakcji** | `StatusBadge` kolorystycznie odróżnia statusy; `:disabled`/`:hover`/`:focus-visible` na wszystkich elementach interaktywnych; błędy inline w formularzach; spinner na przyciskach pending | [OK] |
| V5 | **Estetyczne wykorzystanie SCSS** | SCSS Modules z BEM, `@use '../styles/variables' as *`, zagnieżdżone reguły `&__`, `&--`, CSS custom properties dla motywu (`--clr-bg`, `--clr-text` etc.), media queries przez zmienne | [OK] |
| V6 | **Wykorzystanie wbudowanych komponentów** | Natywne `<input type="date">`, `<input type="time">`, `<select>`, `<textarea>`, `<dialog role="dialog">`; `<dl>/<dt>/<dd>` dla danych rezerwacji; semantyczny HTML5 (`<header>`, `<main>`, `<aside>`) | [OK] |
### Wymagania techniczne BIU ZAO (10)
| # | Kryterium | Realizacja | Status |
|---|-----------|-----------|--------|
| T1 | **Podstawowe hooki React** | `useState` stan formularzy, UI, modali; `useEffect` inicjalizacja auth z localStorage, scroll do karty przez `useRef`; `useContext` `useAuth()`, `useTheme()` w całej aplikacji | [OK] |
| T2 | **Custom hooki** | 7 hooków w `src/hooks/`: `useServices`, `useUserReservations`, `useSlotReservations`, `useReservations`, `useCreateReservation`, `useUpdateReservation`, `useDeleteReservation` | [OK] |
| T3 | **React Router** | React Router v7: `BrowserRouter`, `Routes`, `Route`, `Navigate`, `Outlet`, `Link`, `useNavigate`, `useLocation`, `useSearchParams`; chronione trasy przez `ProtectedRoute` z `allowedRoles` | [OK] |
| T4 | **Zarządzanie stanem (Context API)** | `AuthContext` użytkownik, 2FA (`pendingUser`/`twoFACode`/`verify2FA`), role, sesja; `ThemeContext` motyw jasny/ciemny z `localStorage` i `data-theme` na `<html>` | [OK] |
| T5 | **Obsługa zapytań API** | Axios w `src/api/` dla REST (json-server); Apollo Client `SchemaLink` dla GraphQL (in-memory schema z resolverami w `main.jsx`) | [OK] |
| T6 | **Techniki optymalizacyjne** | `React.lazy` + `Suspense` (DashboardPage, AdminPage); `useMemo` (statystyki w AnalyticsDashboard, maxScore w SlotFinder); `useCallback` (auth functions, Escape handler w Modal) | [OK] |
| T7 | **Wzorce kompozycji komponentów** | HOC `withRole(['admin'])(Component)` z `displayName`; Render props `DataRenderer` delegacja renderowania przez `children` jako funkcję; oba używane w komponentach produkcyjnych | [OK] |
| T8 | **Obsługa formularzy i walidacja** | Formik + Zod (`toFormikValidationSchema`): LoginPage, RegisterPage, ProfileEditModal; `react-hook-form` + `zodResolver`: BookingForm; walidacja krokowa w BookingWizard | [OK] |
| T9 | **Konsekwentny styl kodu** | SCSS BEM, camelCase hooks/functions, PascalCase komponenty, `styles['block__element--modifier']` we wszystkich SCSS Modules, ESLint, jednolite formatowanie | [OK] |
| T10 | **Nowoczesny JavaScript** | Destrukturyzacja: `const { user, logout } = useAuth()`, `const { password: _omit, ...safeUser } = match`; spread: `{ ...prev, [key]: value }`; optional chaining `?.`; nullish `??`; template literals | [OK] |
### Wymagania dodatkowe BIU ZAO (11)
| # | Kryterium | Realizacja | Status |
|---|-----------|-----------|--------|
| D1 | **React Suspense i Error Boundaries** | `ErrorBoundary.jsx` (class component z `componentDidCatch`, fallback UI + przycisk reload); `<Suspense fallback={<LoadingSpinner/>}>` w `App.jsx` opakowuje lazy-loaded strony | [OK] |
| D2 | **Zaawansowana obsługa formularzy** | Formik + `toFormikValidationSchema(zodSchema)`: LoginPage, RegisterPage, ProfileEditModal; `react-hook-form` + `zodResolver`: BookingForm; oba podejścia w jednym projekcie | [OK] |
| D3 | **GraphQL z Apollo Client** | Apollo Client v4 + `makeExecutableSchema` + `SchemaLink`; in-memory resolvery dla `userProfile`, `serviceReviews`, `userReviews`, `submitReview` (store w pamięci); `useQuery`/`useMutation` z `@apollo/client/react` | [OK] |
| D4 | **React Portals** | 4 komponenty mountowane do `#portal-root` przez `createPortal()`: `Modal.jsx`, `PaymentModal.jsx`, `ProfileEditModal.jsx`, `RescheduleModal.jsx` | [OK] |
| D5 | **React.lazy i dynamic imports** | `lazy(() => import('./pages/DashboardPage'))` i `lazy(() => import('./pages/AdminPage'))` w `App.jsx`; ładowanie on-demand przy pierwszym wejściu na trasę | [OK] |
| D6 | **Render props i HOC** | `DataRenderer.jsx` render prop (`children` jako funkcja), używany w `AnalyticsDashboard`; `withRole.jsx` HOC z `displayName`, używany w `AdminPage`; oba wzorce produkcyjne | [OK] |
| D7 | **React Query** | TanStack React Query v5: 7 custom hooków, `staleTime` 5 min, `invalidateQueries` po mutacjach (lista rezerwacji odświeżana po każdej zmianie), `isPending` dla UX | [OK] |
| D8 | **React DevTools** | `WithRole.displayName` dla czytelności drzewa; QueryClient kompatybilny z React Query Devtools; Apollo `InMemoryCache` widoczna w Apollo DevTools | [OK] |
| D9 | **Testy z React Testing Library** | 5 plików testowych, 25+ testów (Vitest): LoginPage (formularz, błędy, submit), BookingForm (walidacja RHF), ProtectedRoute (przekierowania, role), StatusBadge (warianty x2) | [OK] |
| D10 | **Storybook** | 3 pliki stories w `src/stories/`: StatusBadge (wszystkie statusy + rozmiary), Modal (Default + Danger z useState wrapper), ThemeToggle (z etykietą); Storybook 8 + `@storybook/react-vite` | [OK] |
| D11 | **React Concurrent Mode** | `useTransition` w `AdminPage.jsx`: przełączanie zakładek (Tabela/Kalendarz/Analityka/Użytkownicy) przez `startTransition(() => setMainTab(key))` nie blokuje UI przy ciężkich re-renderach | [OK] |
---
## Wyniki testów
### Vitest — testy jednostkowe
```
src/tests/StatusBadge.test.jsx 6 tests
src/tests/ProtectedRoute.test.jsx 4 tests
src/components/StatusBadge.test.jsx 6 tests
src/tests/BookingForm.test.jsx 5 tests
src/tests/LoginPage.test.jsx 4 tests
Test Files 5 passed (5)
Tests 25 passed (25)
Duration 1.22s
```
### Playwright — testy e2e
```
PASS Login page loads (email field visible)
PASS Wrong credentials → error message
PASS Admin login → /admin
PASS Admin table — 56 rows
PASS Confirm button visible
PASS Confirm click updates row to Confirmed
PASS Delete confirmation modal opens
PASS Calendar tab button visible
PASS Calendar view renders month — June 2026
PASS Calendar weekday headers render
WARN No busy day cells visible in current month view
PASS Calendar next-month navigation — July 2026
PASS Admin logout → /login
PASS Client login → /dashboard
PASS AvailabilitySearch section renders
PASS Date syncs to URL
PASS Service syncs to URL
PASS Time slot grid renders (09:00 visible)
PASS Slot click (09:00) jumps wizard to step 3
PASS Wizard renders at step 1 after reload
PASS Service cards render
PASS Wizard step 2 (Date & Time)
PASS Wizard step 3 (Requirements)
PASS Wizard step 4 (Summary)
PASS Confirm & Pay button in step 4
PASS Payment modal opens
PASS Deposit amount displayed — $25.00
PASS Full booking flow — Booking Confirmed shown
WARN Receipt block not visible
PASS My Reservations sidebar renders
PASS My Reservations lists items
PASS Cancel from My Reservations updates status
PASS Dark mode toggle works
PASS Client blocked from /admin → redirected to /dashboard
32 passed · 2 warnings · 0 failed
```