Inicjalna wersja systemu zarządzania rezerwacjami (RMS)

SPA zbudowane w React 19 + Vite 8 z pełnym zestawem funkcjonalności:
autentykacja z 2FA, kreator rezerwacji, panel admina, analityka,
GraphQL (Apollo Client + SchemaLink), React Query, Storybook,
testy jednostkowe (Vitest + RTL) i e2e (Playwright).
This commit is contained in:
Krzysztof Cieślik
2026-06-21 06:08:47 +02:00
commit f436d87ca5
99 changed files with 18696 additions and 0 deletions

247
README.md Normal file
View File

@@ -0,0 +1,247 @@
# 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 (jednostkowe): 25 passed, 0 failed
Playwright (e2e): 32 passed, 0 failed, 2 warnings
```