fix(industrial): improve OSM review UX + public map visibility
Some checks failed
CI / E2E Tests (push) Has been skipped
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 9s
CI / AI Services (Python) — Smoke (push) Failing after 7s
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 1m7s
Deploy / Build API Image (push) Failing after 16s
Deploy / Build Web Image (push) Failing after 6s
Deploy / Build AI Services Image (push) Failing after 7s
E2E Tests / Playwright E2E (push) Failing after 15s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 5s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m13s
Security Scanning / Trivy Scan — Web Image (push) Failing after 49s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 40s
Security Scanning / Trivy Filesystem Scan (push) Failing after 40s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 1s
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Some checks failed
CI / E2E Tests (push) Has been skipped
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 9s
CI / AI Services (Python) — Smoke (push) Failing after 7s
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 1m7s
Deploy / Build API Image (push) Failing after 16s
Deploy / Build Web Image (push) Failing after 6s
Deploy / Build AI Services Image (push) Failing after 7s
E2E Tests / Playwright E2E (push) Failing after 15s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 5s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m13s
Security Scanning / Trivy Scan — Web Image (push) Failing after 49s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 40s
Security Scanning / Trivy Filesystem Scan (push) Failing after 40s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 1s
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Four UX issues surfaced when reviewing the new OSM-sync pipeline against the actual 2,193 imports — fixed in this commit: 1. Admin queue surfaced noise first. `ListOsmPendingHandler` now sorts by `totalAreaHa DESC` (real KCN first, single-factory `landuse=industrial` polygons last) and accepts `minAreaHa` (default 50 ha) plus a `region` filter. The admin page exposes both as dropdowns — "Tất cả / ≥ 5 / ≥ 50 / ≥ 200 / ≥ 500 ha". Top-of-queue is now Bàu Bàng (2,597 ha) and Nhơn Trạch (2,535 ha). 2. Promote dialog said "KCN KCN Đại An" — duplicate prefix. Reworded to "Sắp promote: <name>" so the row name stands on its own. 3. Province was "Chưa xác định" on 2,107 of 2,193 OSM rows. The OSM tags lacked any addr:* hint, so the importer never had anything to write. Added `scripts/data/vn-province-centroids.ts` (63 provinces with capital-city coords) and a `nearestProvince(lat, lng)` fallback in `parseFeature()`. Shipped a one-shot backfill script `scripts/backfill-osm-provinces.ts` and ran it — every existing OSM row now has a province (Hồ Chí Minh: 408, Lạng Sơn: 232, Quảng Ninh: 220, Hà Nội: 172, Hải Phòng: 105, …). Admin can correct on promote if the nearest-centroid heuristic picked the wrong neighbour for a long-thin province. 4. Public map looked empty — only 20 curated parks visible. Added an opt-in toggle "Hiển thị KCN OSM" with a small legend above the map. When on, the bbox endpoint returns OSM raw rows too; markers render in amber (vs. green for curated) at slightly smaller radius and lower opacity, so the visual hierarchy stays clear. Refetch is wired through a ref so the toggle takes effect without remounting the map. Verified in browser preview: zoom-out shows clusters of 320 / 71 / etc. across the country with the toggle on, and just three small clusters (20 curated parks) when off. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,10 @@ export class ListOsmPendingHandler implements IQueryHandler<ListOsmPendingQuery>
|
||||
conditions.push(`province = $${p++}`);
|
||||
values.push(q.province);
|
||||
}
|
||||
if (q.region) {
|
||||
conditions.push(`region::text = $${p++}`);
|
||||
values.push(q.region);
|
||||
}
|
||||
if (q.query) {
|
||||
conditions.push(
|
||||
`(name ILIKE $${p} OR "nameEn" ILIKE $${p} OR developer ILIKE $${p})`,
|
||||
@@ -57,6 +61,13 @@ export class ListOsmPendingHandler implements IQueryHandler<ListOsmPendingQuery>
|
||||
values.push(`%${q.query}%`);
|
||||
p += 1;
|
||||
}
|
||||
if (q.minAreaHa > 0) {
|
||||
// Use COALESCE so rows whose area we couldn't compute (NODE-only
|
||||
// imports) only show up when the admin explicitly drops the floor
|
||||
// to 0.
|
||||
conditions.push(`COALESCE("totalAreaHa", 0) >= $${p++}`);
|
||||
values.push(q.minAreaHa);
|
||||
}
|
||||
const where = conditions.join(' AND ');
|
||||
|
||||
const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: bigint }]>(
|
||||
@@ -95,7 +106,7 @@ export class ListOsmPendingHandler implements IQueryHandler<ListOsmPendingQuery>
|
||||
ST_Y(location::geometry) AS lat, ST_X(location::geometry) AS lng
|
||||
FROM "IndustrialPark"
|
||||
WHERE ${where}
|
||||
ORDER BY "lastSyncedAt" DESC NULLS LAST, "totalAreaHa" DESC NULLS LAST
|
||||
ORDER BY "totalAreaHa" DESC NULLS LAST, "lastSyncedAt" DESC NULLS LAST
|
||||
LIMIT $${p++} OFFSET $${p}`,
|
||||
...values,
|
||||
limit,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
/**
|
||||
* Admin OSM review queue — list raw OSM-imported parks that haven't yet
|
||||
* been promoted to the public catalogue.
|
||||
*
|
||||
* `minAreaHa` lets admins skip the long tail of `landuse=industrial`
|
||||
* features OSM tags that turn out to be single factories or warehouses
|
||||
* (typically < 5 ha). The default of 50 ha surfaces "real" KCN first; pass
|
||||
* `0` to see everything.
|
||||
*/
|
||||
export class ListOsmPendingQuery {
|
||||
constructor(
|
||||
@@ -8,5 +13,7 @@ export class ListOsmPendingQuery {
|
||||
public readonly limit: number = 50,
|
||||
public readonly query?: string,
|
||||
public readonly province?: string,
|
||||
public readonly minAreaHa: number = 50,
|
||||
public readonly region?: string,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -302,6 +302,8 @@ export class IndustrialParksController {
|
||||
@Query('limit') limit?: string,
|
||||
@Query('q') q?: string,
|
||||
@Query('province') province?: string,
|
||||
@Query('minAreaHa') minAreaHa?: string,
|
||||
@Query('region') region?: string,
|
||||
) {
|
||||
return this.queryBus.execute(
|
||||
new ListOsmPendingQuery(
|
||||
@@ -309,6 +311,8 @@ export class IndustrialParksController {
|
||||
limit ? parseInt(limit, 10) : 50,
|
||||
q,
|
||||
province,
|
||||
minAreaHa !== undefined ? Number(minAreaHa) : 50,
|
||||
region,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user