feat(places): enrich list-imported places via the Places API (#886) (#1161)

* feat(places): enrich list-imported places via the Places API (#886)

Google/Naver list imports only carry a name and coordinates, so the places open
as bare pins — the Maps tab jumps to coordinates, with no photo, address or
open/closed. Add an opt-in "Enrich places via Google" toggle to the list-import
dialog, shown only when a Google Maps key is configured.

When enabled, after the (fast, unchanged) import the server runs a background
pass that re-resolves each place by name — biased to and validated against the
imported coordinates so a common-name search cannot overwrite the wrong place —
and fills the empty address/website/phone/photo columns plus the resolved
google_place_id, pushing each row over the live sync. Opening hours and the
proper Maps link then work on demand from the stored id.

Enrichment only fills empty fields, runs detached so a long list never blocks
the import, and no-ops when no key is configured.

* fix(places): use the ToggleSwitch component for the enrich toggle

Match the rest of the app — the import-enrichment opt-in used a raw checkbox;
swap it for the shared ToggleSwitch (text left, switch right) like the settings
toggles.
This commit is contained in:
Maurice
2026-06-14 00:54:11 +02:00
committed by GitHub
parent 3398da633b
commit 3e9626fce9
29 changed files with 331 additions and 18 deletions
+18 -1
View File
@@ -13,6 +13,14 @@ import {
resolveCategoryIdForFolder,
type KmlImportSummary,
} from './kmlImport';
import { enrichImportedPlaces, type EnrichablePlace } from './placeEnrichment';
/** Opt-in Places-API enrichment for list imports (#886). */
export interface ListImportOptions {
enrich?: boolean;
userId?: number;
lang?: string;
}
interface PlaceWithCategory extends Place {
category_name: string | null;
@@ -595,7 +603,7 @@ export async function importMapFile(tripId: string, fileBuffer: Buffer, filename
// Import Google Maps list
// ---------------------------------------------------------------------------
export async function importGoogleList(tripId: string, url: string) {
export async function importGoogleList(tripId: string, url: string, opts?: ListImportOptions) {
let listId: string | null = null;
let resolvedUrl = url;
@@ -697,6 +705,10 @@ export async function importGoogleList(tripId: string, url: string) {
});
insertAll();
if (opts?.enrich && opts.userId && created.length) {
void enrichImportedPlaces(tripId, opts.userId, created as EnrichablePlace[], opts.lang);
}
return { places: created, listName, skipped };
}
@@ -707,6 +719,7 @@ export async function importGoogleList(tripId: string, url: string) {
export async function importNaverList(
tripId: string,
url: string,
opts?: ListImportOptions,
): Promise<{ places: any[]; listName: string; skipped: number } | { error: string; status: number }> {
let resolvedUrl = url;
const limit = 20;
@@ -826,6 +839,10 @@ export async function importNaverList(
});
insertAll();
if (opts?.enrich && opts.userId && created.length) {
void enrichImportedPlaces(tripId, opts.userId, created as EnrichablePlace[], opts.lang);
}
return { places: created, listName, skipped };
}