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).
248 lines
18 KiB
Markdown
248 lines
18 KiB
Markdown
# 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 (1–5) + 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
|
||
```
|