Ctrl K
ComunidadGuías…

src/lib/locations.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// A place, between the geocoder and the two forms that record one. Both the
// dive site form and a trip part's row fill the same object from the same
// response, so the mapping is written once here rather than slightly
// differently in each of them.

import type { GeocodeResult } from "@/lib/api/geocoding";
import type { LocationFormValue } from "@/lib/validations/location";

/**
 * A geocoder result as a place.
 *
 * `name` is the API's `location` as it arrives - the place, its region and its
 * country, "Ko Tao, Surat Thani Province, Thailand" - for the site dialog and
 * the trip form alike. The API composes it once, for a typed search and a
 * dropped pin both, so nothing here rebuilds it from parts.
 *
 * The centre and the box are the *place's*, which is what a forward search
 * answers with. A reverse geocode must not go through here: the coordinates it
 * returns are the host's own position, so adopting them would file the pin a
 * diver dropped as the centre of the town around it.
 */
export function geocodeResultToLocation(
  result: GeocodeResult,
): LocationFormValue {
  return {
    name: result.location,
    latitude: result.latitude,
    longitude: result.longitude,
    bbox_south: result.bbox_south,
    bbox_north: result.bbox_north,
    bbox_west: result.bbox_west,
    bbox_east: result.bbox_east,
  };
}

/**
 * Where a place sits, as one string: `region, country` where both are known,
 * whichever one is where only one is, and `null` where neither is.
 *
 * What the dive site search shows for a geocoder row.
 */
export function formatPlaceContext(
  region?: string | null,
  country?: string | null,
): string | null {
  const parts = [region, country].filter(
    (part): part is string => !!part?.trim(),
  );
  return parts.length > 0 ? parts.join(", ") : null;
}

/**
 * A geocoder result as the dive site search shows it: the place's own name, and
 * where it is as `region, country` - "Moalboal" and "Cebu, Philippines", read
 * together as "Moalboal, Cebu, Philippines".
 *
 * Read as one line, the row says what picking it saves: the API's `location` is
 * the same three parts. A result that matched an address has no name of its
 * own, so it reads as its `location` alone, which already holds every part a
 * hint would add. A context part the name already says is dropped - the country
 * row "Philippines" gets no context at all.
 */
export function geocodeResultLabel(result: GeocodeResult): {
  name: string;
  context: string | null;
} {
  const name = result.name;
  if (!name) return { name: result.location, context: null };
  const region = unrepeated(result.region, name);
  const country = unrepeated(result.country, name, region);
  return { name, context: formatPlaceContext(region, country) };
}

/**
 * `part`, unless it only repeats a comma-separated part of one of `labels` -
 * compared whole and case-insensitively, so "Cebu" repeats "Moalboal, Cebu" but
 * not "Cebu City". `null` for a repeat and for a blank.
 *
 * A menu row joins its name and hint with ", ", so a hint part the name already
 * holds reads twice: the country row "Philippines" would otherwise say
 * "Philippines, Philippines".
 */
export function unrepeated(
  part: string | null | undefined,
  ...labels: (string | null | undefined)[]
): string | null {
  const trimmed = part?.trim();
  if (!trimmed) return null;
  const said = labels.flatMap((label) =>
    (label ?? "").split(",").map((each) => each.trim().toLowerCase()),
  );
  return said.includes(trimmed.toLowerCase()) ? null : trimmed;
}