Skip to content

Commit 6a04213

Browse files
committed
feat: add community picker with address search and welcome dialog to map view
Redesign the map sidebar into a dual-mode panel: a simplified community picker (normal mode) with address search via LocationSelector, and the full layer controls (editing mode) for admins. Citizens can search their address to find nearby collection points within 500m, shown as colored pins on the map. A welcome dialog presents the regulation summary on first load with navigation options. Also: extract shared types (CurrentUser, GeoSetData, SEARCH_COLORS) to types.ts, deduplicate geometry list item rendering in DetailPanel, add GeometryCollection support to bounds calculation, improve map labels (point addresses at zoom, polygon names that fade), and update docs.
1 parent 20da74e commit 6a04213

File tree

18 files changed

+993
-459
lines changed

18 files changed

+993
-459
lines changed

CLAUDE.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44

5+
## Development Environment
6+
7+
This project uses [Nix](https://nixos.org/) to manage development dependencies. All shell commands (`npm`, `npx`, `node`, etc.) must be run inside the Nix development shell.
8+
9+
**Prefix all commands with `nix develop --command`**, for example:
10+
```bash
11+
nix develop --command npm run build
12+
nix develop --command npx tsc --noEmit
13+
```
14+
15+
If you open an interactive shell session first (`nix develop`), subsequent commands in that session don't need the prefix.
16+
517
## Build Commands
618

719
### Development

docs/guides/consultations.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The consultation feature operates as a JSON-driven, dual-view interface:
1010

1111
1. **Regulation JSON**: Each consultation points to a remote JSON file (`jsonUrl`) that defines the entire regulation structure — chapters, articles, geographic areas, cross-references, and definitions. The schema is defined in [`json-schemas/regulation.schema.json`](../../json-schemas/regulation.schema.json).
1212
2. **Database Layer**: Prisma stores consultation metadata (name, end date, active status), comments, and upvotes. Comments are entity-scoped — tied to a specific chapter, article, geoset, or geometry by `entityType` + `entityId`.
13-
3. **Frontend Layer**: A `ConsultationViewer` client component orchestrates two views — a Document View (chapters/articles with markdown content) and a Map View (Mapbox-powered geographic visualization). A floating action button toggles between them.
13+
3. **Frontend Layer**: A `ConsultationViewer` client component orchestrates two views — a Document View (chapters/articles with markdown content) and a Map View (Mapbox-powered geographic visualization). A floating action button toggles between them. The map view features a community picker with address search, allowing citizens to find nearby collection points by searching their address. A welcome dialog shows the regulation summary on first load.
1414
4. **Comment System**: Authenticated users can leave HTML-rich comments on any entity. Comments support upvoting and trigger email notifications to the municipality's contact address.
1515
5. **Admin Geo-Editor**: Administrators can draw missing geometries directly on the map when regulation text defines areas textually but lacks GeoJSON coordinates. Edits are stored in localStorage and exported as a complete updated regulation JSON.
1616

@@ -121,15 +121,15 @@ sequenceDiagram
121121
* Layout: [`src/app/[locale]/(city)/[cityId]/consultation/[id]/layout.tsx`](../../src/app/%5Blocale%5D/(city)/%5BcityId%5D/consultation/%5Bid%5D/layout.tsx) (header, footer, feature-flag check)
122122

123123
* **Frontend Components** (all under `src/components/consultations/`):
124-
* `ConsultationViewer`: Master orchestrator — manages view state (document/map), URL hash navigation, chapter expansion, reference click handling
124+
* `ConsultationViewer`: Master orchestrator — manages view state (document/map), URL hash navigation, chapter expansion, reference click handling, welcome dialog with regulation summary, and `defaultView` support
125125
* `ConsultationHeader`: Title, status badge (Active/Inactive), end date, comment count
126126
* `ConsultationDocument`: Renders chapters/articles with expand/collapse, AI summary cards, sources list
127127
* `ChapterView` / `ArticleView`: Individual chapter and article renderers with comment counts, permalinks, collapsible content
128128
* `MarkdownContent`: Renders markdown with `{REF:id}` and `{DEF:id}` pattern handling as interactive links
129-
* `ConsultationMap`: Mapbox map with geoset rendering, layer controls, detail panel, derived geometry computation (buffer/difference)
130-
* `LayerControlsPanel` / `LayerControlsButton`: Sidebar for toggling geoset/geometry visibility with checkbox tree UI
131-
* `DetailPanel`: Side sheet showing selected geoset/geometry info with description, textual definition, and comments
132-
* `GeoSetItem` / `GeometryItem`: Tree items in layer controls with checkboxes, color swatches, and comment counts
129+
* `ConsultationMap`: Mapbox map with geoset rendering, layer controls, detail panel, derived geometry computation (buffer/difference), address search with search location pins, initial fit-to-bounds, and `GeometryCollection` zoom support
130+
* `LayerControlsPanel` / `LayerControlsButton`: Dual-mode sidebar — in normal mode shows a simplified community picker with address search (via `LocationSelector`); in editing mode shows the full layer controls with checkbox tree UI for toggling geoset/geometry visibility
131+
* `DetailPanel`: Side sheet showing selected geoset/geometry/search-location info. For search locations, shows nearby points within 500m sorted by distance (Haversine). For geosets, lists point geometries with comment counts. For geometries, shows description, textual definition, and comments
132+
* `GeoSetItem` / `GeometryItem`: Tree items in layer controls (editing mode) with checkboxes, color swatches, clickable names, and inline comment counts
133133
* `CommentSection`: Rich text editor (ReactQuill), authentication check, comment display with upvotes and delete
134134
* `CommentsOverviewSheet`: Modal listing all comments with sort options (recent/likes), entity type badges, navigation
135135
* `AISummaryCard`: Collapsible card for AI-generated summaries on chapters/articles
@@ -144,7 +144,7 @@ sequenceDiagram
144144
* `CityConsultations`: [`src/components/cities/CityConsultations.tsx`](../../src/components/cities/CityConsultations.tsx) (card grid listing for city consultations tab)
145145

146146
* **Types**:
147-
* `RegulationData`, `Chapter`, `Article`, `GeoSet`, `Geometry`, etc.: [`src/components/consultations/types.ts`](../../src/components/consultations/types.ts)
147+
* `RegulationData`, `Geometry`, `CurrentUser`, `GeoSetData`, `SEARCH_COLORS`, etc.: [`src/components/consultations/types.ts`](../../src/components/consultations/types.ts) (shared types used across all consultation components)
148148

149149
* **Email**:
150150
* Template: [`src/lib/email/templates/consultation-comment.tsx`](../../src/lib/email/templates/consultation-comment.tsx) (React Email HTML template with entity permalink)
@@ -261,12 +261,16 @@ For local development, you can place regulation JSON files in the `public/` dire
261261
4. Geometries may have a `textualDefinition` but null `geojson` — the admin geo-editor addresses this gap
262262

263263
### Map & Geo-Editor
264-
1. The map uses Mapbox GL with custom styling for different geosets (each has a `color`)
264+
1. The map uses Mapbox GL with custom styling for different geosets (each has a `color`) and always-on street labels
265265
2. `defaultVisibleGeosets` in the regulation JSON controls initial map layer visibility
266-
3. Derived geometries are computed client-side using Turf.js operations
267-
4. The admin geo-editor stores drawn geometries in browser `localStorage` until exported
268-
5. Export produces a complete updated `regulation.json` merging local edits with original data
269-
6. Only super-administrators can access editing mode
266+
3. The map auto-fits to all visible features on initial load (unless a hash navigation targets a specific entity)
267+
4. Citizens can search addresses via the community picker; searched locations appear as colored pins and open a detail panel showing nearby points within 500m
268+
5. Clicking a community boundary polygon opens the parent geoset detail; clicking a point opens the geometry detail
269+
6. Point labels (addresses) appear at higher zoom levels; polygon labels (community names) fade out at street level to avoid noise
270+
7. Derived geometries are computed client-side using buffer/difference operations
271+
8. The admin geo-editor stores drawn geometries in browser `localStorage` until exported
272+
9. Export produces a complete updated `regulation.json` merging local edits with original data
273+
10. Only super-administrators can access editing mode (via a small edit icon in the community picker header)
270274

271275
### Navigation
272276
1. URL hash anchors (`#chapter-1`, `#article-3`, `#geoset-prohibited_areas`) enable deep linking to specific entities

jest.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
module.exports = {
22
preset: 'ts-jest',
33
testEnvironment: 'jsdom',
4+
maxWorkers: '50%',
5+
workerIdleMemoryLimit: '512MB',
46
moduleNameMapper: {
57
'^@/(.*)$': '<rootDir>/src/$1',
68
},

public/regulation-cooking-oil.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"description": "Η σελίδα της δημόσιας διαβούλευσης στον ιστότοπο του Δήμου Αθηναίων"
1515
}
1616
],
17+
"defaultView": "map",
1718
"defaultVisibleGeosets": [
1819
"dk_1",
1920
"dk_2",
@@ -4402,11 +4403,11 @@
44024403
"id": "dk2_23_point",
44034404
"textualDefinition": "ΠΛ. ΠΡΟΦ. ΗΛΙΑ, 2η Δημοτική Κοινότητα, Αθήνα",
44044405
"geojson": {
4405-
"type": "Point",
44064406
"coordinates": [
4407-
23.7535381,
4408-
38.0123173
4409-
]
4407+
23.7465002258959,
4408+
37.965898200067805
4409+
],
4410+
"type": "Point"
44104411
}
44114412
},
44124413
{
@@ -11978,8 +11979,8 @@
1197811979
"geojson": {
1197911980
"type": "Point",
1198011981
"coordinates": [
11981-
23.751698,
11982-
37.9442429
11982+
23.7734905,
11983+
38.0003983
1198311984
]
1198411985
}
1198511986
},

src/app/[locale]/(city)/[cityId]/consultation/[id]/page.tsx

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,15 @@
11
import { Metadata } from "next";
22
import { getCityCached } from "@/lib/cache";
3-
import { getConsultationById, getConsultationComments } from "@/lib/db/consultations";
3+
import { getConsultationById, getConsultationComments, fetchRegulationData } from "@/lib/db/consultations";
44
import { notFound } from "next/navigation";
55
import { ConsultationViewer } from "@/components/consultations";
6-
import { RegulationData } from "@/components/consultations/types";
76
import { auth } from "@/auth";
87
import { env } from "@/env.mjs";
98

109
interface PageProps {
1110
params: { cityId: string; id: string };
1211
}
1312

14-
async function fetchRegulationData(jsonUrl: string): Promise<RegulationData | null> {
15-
try {
16-
// Resolve relative URLs (e.g. /regulation.json) against the app's base URL
17-
const url = jsonUrl.startsWith('http') ? jsonUrl : `${env.NEXTAUTH_URL}${jsonUrl}`;
18-
const response = await fetch(url, { cache: 'no-store' });
19-
20-
if (!response.ok) {
21-
console.error(`Failed to fetch regulation data: ${response.status}`);
22-
return null;
23-
}
24-
25-
return await response.json();
26-
} catch (error) {
27-
console.error('Error fetching regulation data:', error);
28-
return null;
29-
}
30-
}
31-
3213
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
3314
const [consultation, city] = await Promise.all([
3415
getConsultationById(params.cityId, params.id),
@@ -120,7 +101,7 @@ export default async function ConsultationPage({ params }: PageProps) {
120101
}
121102

122103
// Check if consultations are enabled for this city
123-
if (!(city as any).consultationsEnabled) {
104+
if (!city.consultationsEnabled) {
124105
notFound();
125106
}
126107

@@ -182,6 +163,8 @@ export default async function ConsultationPage({ params }: PageProps) {
182163
currentUser={session?.user}
183164
consultationId={params.id}
184165
cityId={params.cityId}
166+
cityName={city.name}
167+
cityLogoUrl={city.logoImage || null}
185168
/>
186169
</>
187170
);

src/components/consultations/ArticleView.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,9 @@ import PermalinkButton from "./PermalinkButton";
44
import AISummaryCard from "./AISummaryCard";
55
import MarkdownContent from "./MarkdownContent";
66
import CommentSection from "./CommentSection";
7-
import { Article, ReferenceFormat, RegulationData } from "./types";
7+
import { Article, ReferenceFormat, RegulationData, CurrentUser } from "./types";
88
import { ConsultationCommentWithUpvotes } from "@/lib/db/consultations";
99

10-
interface CurrentUser {
11-
id?: string;
12-
name?: string | null;
13-
email?: string | null;
14-
}
15-
1610
interface ArticleViewProps {
1711
article: Article;
1812
baseUrl: string;

src/components/consultations/ChapterView.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,9 @@ import PermalinkButton from "./PermalinkButton";
44
import AISummaryCard from "./AISummaryCard";
55
import MarkdownContent from "./MarkdownContent";
66
import CommentSection from "./CommentSection";
7-
import { RegulationItem, ReferenceFormat, RegulationData } from "./types";
7+
import { RegulationItem, ReferenceFormat, RegulationData, CurrentUser } from "./types";
88
import { ConsultationCommentWithUpvotes } from "@/lib/db/consultations";
99

10-
interface CurrentUser {
11-
id?: string;
12-
name?: string | null;
13-
email?: string | null;
14-
}
15-
1610
interface ChapterViewProps {
1711
chapter: RegulationItem;
1812
baseUrl: string;

src/components/consultations/ConsultationDocument.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,9 @@ import ArticleView from "./ArticleView";
1111
import DocumentNavigation from "./DocumentNavigation";
1212
import SourcesList from "./SourcesList";
1313
import MarkdownContent from "./MarkdownContent";
14-
import { RegulationData, ReferenceFormat } from "./types";
14+
import { RegulationData, ReferenceFormat, CurrentUser } from "./types";
1515
import { ConsultationCommentWithUpvotes } from "@/lib/db/consultations";
1616

17-
interface CurrentUser {
18-
id?: string;
19-
name?: string | null;
20-
email?: string | null;
21-
}
22-
2317
interface ConsultationDocumentProps {
2418
regulationData: RegulationData | null;
2519
baseUrl: string; // Base URL for permalinks

0 commit comments

Comments
 (0)