Add Phase 4 master plan and install leaflet dependency

Write the flights-map master plan covering sub-plans 4A-4D.
Add leaflet + @types/leaflet to support the map canvas component.
This commit is contained in:
2026-04-15 09:44:01 +03:00
parent a60494366f
commit 8dde2db9d2
3 changed files with 102 additions and 0 deletions
@@ -0,0 +1,77 @@
# Phase 4 -- Flights Map (Master Plan)
## Goal
Port the Angular `flights-map` feature (single `/flights-map` route) to the React/ModernJS codebase as a Module Federation remote component. The map uses Leaflet for interactive marker/polyline rendering and is behind the `flightsMap` feature flag.
## Angular analysis summary
- **Route:** `/flights-map` (start page with embedded Leaflet map)
- **Feature flag:** `flightsMap` (env-based)
- **APIs:** `destinations/v1` (search routes by departure/arrival), `days/.../flights-map/v1` (calendar availability)
- **Leaflet map:** markers (blue default, blue-small zoom-dependent, orange selected), polylines (solid direct, dashed connecting), popups, tooltips, zoom-aware layer visibility
- **Components:** start page container, map body (sole Leaflet consumer), filter panel, meta tags, loader/empty overlays
- **No SignalR** -- data is static, fetched on filter change
- **SEO:** meta tags only in Angular; design spec requires JSON-LD `WebPage` schema
## Sub-plans
### 4A -- Types + API + Hooks
**Files:** `src/features/flights-map/types.ts`, `api.ts`, `api.test.ts`, `hooks/useFlightsMapSearch.ts`, `hooks/useFlightsMapCalendar.ts`
- Define `IFlightRoute`, `IMapMarker`, `IMapPolyline`, `FlightsMapSearchParams`, `FlightsMapCalendarParams`
- Define `IDestinationsResponse`, `IFlightsMapDaysResponse`
- API: `searchDestinations(client, params)` -> `GET destinations/1`, `getFlightsMapCalendar(client, params)` -> `GET days/{date}/200/{route}/flights-map/v1`
- Hooks: `useFlightsMapSearch(params)`, `useFlightsMapCalendar(params)`
- Feature flag: `useFeatureFlag("flightsMap")` via env config
- TDD: ~10 tests for API functions
### 4B -- Leaflet Map Wrapper
**File:** `src/features/flights-map/components/MapCanvas.tsx`
- **Only file that imports `leaflet`** (design spec constraint)
- Wrapped in `React.lazy()` + `<ClientOnly>` (SSR-safe)
- Accepts `markers`, `polylines`, `onMarkerClick` etc. as props -- does not own state
- Renders markers (blue/orange icons), polylines (solid/dashed), popups, tooltips
- Great-circle arc calculation for polyline rendering
- Install `leaflet` + `@types/leaflet`
- No TDD (Leaflet requires real DOM)
### 4C -- Route Pages + Feature Components
**Files:**
- `src/routes/[lang]/flights-map/page.tsx` -- route page with SEO + feature flag guard
- `src/features/flights-map/components/FlightsMapStartPage.tsx` -- container (filter + map + loading/empty states)
- `src/features/flights-map/components/FlightsMapFilter.tsx` -- search filter (departure, arrival, connections, domestic/international toggles, date picker)
- `src/features/flights-map/components/ClientOnly.tsx` -- SSR-safe wrapper
### 4D -- SEO + JSON-LD + Parity
**Files:**
- `src/features/flights-map/seo.ts` -- `buildFlightsMapSeo(t, locale, canonicalOrigin)`
- `src/features/flights-map/json-ld.ts` -- JSON-LD `WebPage` schema builder
- `src/features/flights-map/seo.test.ts` -- ~5 integration tests
- Update barrel `src/features/flights-map/index.ts`
- Update `src/mf/expose/FlightsMap.tsx` from stub to real
## Dependency graph
```
4A (types/api/hooks) --> 4B (map canvas, needs types)
--> 4C (pages/components, needs hooks)
4A + 4B + 4C --------> 4D (SEO + barrel + MF expose update)
```
## Verification
```bash
pnpm typecheck && pnpm lint && pnpm test && pnpm build:standalone
```
## Constraints
- Do NOT touch `ClientApp/`, ASP.NET, `wwwroot/`
- No `Co-Authored-By` lines in commits
- ~8-12 commits total
+2
View File
@@ -35,6 +35,7 @@
"i18next-icu": "^2.0.0", "i18next-icu": "^2.0.0",
"i18next-resources-to-backend": "^1.0.0", "i18next-resources-to-backend": "^1.0.0",
"intl-messageformat": "^10.0.0", "intl-messageformat": "^10.0.0",
"leaflet": "^1.9.4",
"lru-cache": "^10.0.0", "lru-cache": "^10.0.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@@ -48,6 +49,7 @@
"@testing-library/jest-dom": "^6", "@testing-library/jest-dom": "^6",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@testing-library/react-hooks": "^8.0.1", "@testing-library/react-hooks": "^8.0.1",
"@types/leaflet": "^1.9.21",
"@types/node": "^24.0.0", "@types/node": "^24.0.0",
"@types/react": "^18.2.0", "@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0", "@types/react-dom": "^18.2.0",
+23
View File
@@ -50,6 +50,9 @@ importers:
intl-messageformat: intl-messageformat:
specifier: ^10.0.0 specifier: ^10.0.0
version: 10.7.18 version: 10.7.18
leaflet:
specifier: ^1.9.4
version: 1.9.4
lru-cache: lru-cache:
specifier: ^10.0.0 specifier: ^10.0.0
version: 10.4.3 version: 10.4.3
@@ -84,6 +87,9 @@ importers:
'@testing-library/react-hooks': '@testing-library/react-hooks':
specifier: ^8.0.1 specifier: ^8.0.1
version: 8.0.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react-test-renderer@19.2.5(react@18.3.1))(react@18.3.1) version: 8.0.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react-test-renderer@19.2.5(react@18.3.1))(react@18.3.1)
'@types/leaflet':
specifier: ^1.9.21
version: 1.9.21
'@types/node': '@types/node':
specifier: ^24.0.0 specifier: ^24.0.0
version: 24.12.2 version: 24.12.2
@@ -2711,6 +2717,9 @@ packages:
'@types/estree@1.0.8': '@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/geojson@7946.0.16':
resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
'@types/html-minifier-terser@6.1.0': '@types/html-minifier-terser@6.1.0':
resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==}
@@ -2726,6 +2735,9 @@ packages:
'@types/json-schema@7.0.15': '@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/leaflet@1.9.21':
resolution: {integrity: sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==}
'@types/loadable__component@5.13.10': '@types/loadable__component@5.13.10':
resolution: {integrity: sha512-2/LjmgG1JcGPj7T3NViq7BB5cvOA0s63gL3Gv+FPULj2L+3ExWfsNQcsFPUIOoGsVUJeZxgNPf320JZDyxjtCQ==} resolution: {integrity: sha512-2/LjmgG1JcGPj7T3NViq7BB5cvOA0s63gL3Gv+FPULj2L+3ExWfsNQcsFPUIOoGsVUJeZxgNPf320JZDyxjtCQ==}
@@ -4435,6 +4447,9 @@ packages:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
leaflet@1.9.4:
resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==}
levn@0.4.1: levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -9701,6 +9716,8 @@ snapshots:
'@types/estree@1.0.8': {} '@types/estree@1.0.8': {}
'@types/geojson@7946.0.16': {}
'@types/html-minifier-terser@6.1.0': {} '@types/html-minifier-terser@6.1.0': {}
'@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-coverage@2.0.6': {}
@@ -9715,6 +9732,10 @@ snapshots:
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
'@types/leaflet@1.9.21':
dependencies:
'@types/geojson': 7946.0.16
'@types/loadable__component@5.13.10': '@types/loadable__component@5.13.10':
dependencies: dependencies:
'@types/react': 18.3.28 '@types/react': 18.3.28
@@ -11708,6 +11729,8 @@ snapshots:
kind-of@6.0.3: {} kind-of@6.0.3: {}
leaflet@1.9.4: {}
levn@0.4.1: levn@0.4.1:
dependencies: dependencies:
prelude-ls: 1.2.1 prelude-ls: 1.2.1