feat: enhance extractIsoCode function to handle cases without city information and update CollectionMap to bind user data

This commit is contained in:
Sean Morley
2026-01-04 16:21:59 -05:00
parent db89c4e2b7
commit 1651e7dfa7
3 changed files with 159 additions and 4 deletions

View File

@@ -294,6 +294,8 @@ def extractIsoCode(user, data):
display_name = f"{city.name}, {region.name}, {country_code or region.country.country_code}"
visited_city = VisitedCity.objects.filter(city=city, user=user).first()
city_visited = bool(visited_city)
else:
display_name = f"{region.name}, {country_code or region.country.country_code}"
return {
"region_id": iso_code,

View File

@@ -1,12 +1,17 @@
<script lang="ts">
import FullMap, { type FullMapFeatureCollection } from '$lib/components/map/FullMap.svelte';
import { GeoJSON, LineLayer, Marker } from 'svelte-maplibre';
import { Marker } from 'svelte-maplibre';
import { goto } from '$app/navigation';
import { getActivityColor } from '$lib';
import SearchIcon from '~icons/mdi/magnify';
import type { Collection } from '$lib/types';
import Plus from '~icons/mdi/plus';
import PinIcon from '~icons/mdi/map-marker';
import Clear from '~icons/mdi/close';
import NewLocationModal from '$lib/components/locations/LocationModal.svelte';
import type { Collection, Location, User } from '$lib/types';
export let collection: Collection;
export let user: User | null = null;
// Allow disabling/enabling clustering for markers
export let clusterEnabled: boolean = false;
export let clusterOptions: any = { radius: 300, maxZoom: 8, minPoints: 2 };
@@ -52,6 +57,13 @@
let mapZoom = 8;
let mapCenterCoords: [number, number] = [0, 0];
// Creation state
let createModalOpen = false;
let initialLatLng: { lat: number; lng: number } | null = null;
let newMarker: { lngLat: { lng: number; lat: number } } | null = null;
let newLatitude: number | null = null;
let newLongitude: number | null = null;
const defaultClusterOptions = { radius: 300, maxZoom: 8, minPoints: 2 };
$: resolvedClusterOptions = clusterOptions || defaultClusterOptions;
@@ -428,6 +440,62 @@
? markerGeoJson.features[0].geometry.coordinates
: ([0, 0] as [number, number]);
function handleMapClick(e: CustomEvent<{ lngLat: { lng: number; lat: number } }>) {
newMarker = { lngLat: e.detail.lngLat };
newLongitude = e.detail.lngLat.lng;
newLatitude = e.detail.lngLat.lat;
}
function openCreateModal() {
initialLatLng = newMarker ? { lat: newMarker.lngLat.lat, lng: newMarker.lngLat.lng } : null;
createModalOpen = true;
}
function clearNewMarker() {
newMarker = null;
newLatitude = null;
newLongitude = null;
}
function upsertLocation(newLocation: Location) {
const existingLocations = collection?.locations || [];
const idx = existingLocations.findIndex((loc) => loc.id === newLocation.id);
const nextLocations =
idx === -1
? [...existingLocations, newLocation]
: existingLocations.map((loc, i) => (i === idx ? { ...loc, ...newLocation } : loc));
collection = { ...collection, locations: nextLocations };
}
function handleLocationCreated(event: CustomEvent<Location>) {
upsertLocation(event.detail);
const lat = parseNumber(event.detail.latitude);
const lon = parseNumber(event.detail.longitude);
if (lat !== null && lon !== null) {
mapCenterCoords = [lon, lat];
mapZoom = 12;
}
createModalOpen = false;
clearNewMarker();
}
function handleLocationSaved(event: CustomEvent<Location>) {
upsertLocation(event.detail);
const lat = parseNumber(event.detail.latitude);
const lon = parseNumber(event.detail.longitude);
if (lat !== null && lon !== null) {
mapCenterCoords = [lon, lat];
mapZoom = 12;
}
createModalOpen = false;
clearNewMarker();
}
function toggleCategory(name: string) {
const next = new Set(selectedCategories);
if (next.has(name)) {
@@ -717,6 +785,67 @@
</div>
</div>
<!-- Add to Collection CTA (compact) -->
<div class="card bg-base-100 shadow-sm mb-3 border border-base-200">
<div class="card-body py-3 px-4 gap-2">
<div class="flex items-center justify-between gap-3">
<div class="flex items-center gap-2 min-w-0">
<span
class="inline-flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary"
>
<Plus class="w-4 h-4" />
</span>
<div class="min-w-0">
<p class="text-sm font-semibold leading-tight truncate">Add to this collection</p>
<p class="text-xs text-base-content/60 leading-tight truncate">
Click the map to drop a marker, then add it here.
</p>
</div>
</div>
<div class="flex items-center gap-2">
<button type="button" class="btn btn-primary btn-xs" on:click={openCreateModal}>
<Plus class="w-4 h-4" />
Add
</button>
{#if newMarker}
<button type="button" class="btn btn-ghost btn-xs" on:click={clearNewMarker}>
<Clear class="w-4 h-4" />
Clear
</button>
{/if}
</div>
</div>
{#if newMarker}
<div
class="alert alert-info alert-sm flex flex-col sm:flex-row sm:items-center gap-2 py-2 px-3"
>
<div class="flex items-center gap-2 text-xs sm:text-sm">
<PinIcon class="w-4 h-4" />
<span class="truncate">
{newLatitude?.toFixed(4)}, {newLongitude?.toFixed(4)}
</span>
</div>
<div class="flex gap-2 sm:ml-auto">
<button
type="button"
class="btn btn-primary btn-xxs sm:btn-xs"
on:click={openCreateModal}
>
<Plus class="w-3 h-3 sm:w-4 sm:h-4" />
Add here
</button>
<button type="button" class="btn btn-ghost btn-xxs sm:btn-xs" on:click={clearNewMarker}>
<Clear class="w-3 h-3 sm:w-4 sm:h-4" />
</button>
</div>
</div>
{:else}
<p class="text-xs text-base-content/60">Tip: click the map to place a marker.</p>
{/if}
</div>
</div>
<!-- Map -->
<div class="w-full" style="min-height:600px; height:600px;">
<FullMap
@@ -726,6 +855,7 @@
mapClass="w-full h-[600px]"
{clusterEnabled}
clusterOptions={resolvedClusterOptions}
on:mapClick={handleMapClick}
>
<svelte:fragment slot="marker" let:markerProps let:markerLngLat let:isActive let:setActive>
{#if markerProps && markerLngLat}
@@ -816,9 +946,32 @@
</Marker>
{/if}
</svelte:fragment>
<svelte:fragment slot="overlays">
{#if newMarker}
<Marker lngLat={[newMarker.lngLat.lng, newMarker.lngLat.lat]} class="map-pin">
<div
class="map-pin-hit grid place-items-center w-10 h-10 rounded-full bg-primary text-primary-content border-2 border-base-100 shadow-lg"
>
<Plus class="w-5 h-5" />
</div>
</Marker>
{/if}
</svelte:fragment>
</FullMap>
</div>
{#if createModalOpen}
<NewLocationModal
on:create={handleLocationCreated}
on:save={handleLocationSaved}
on:close={() => (createModalOpen = false)}
{initialLatLng}
{collection}
{user}
/>
{/if}
<style>
:global(.min-h-\[600px\]) {
min-height: 600px;

View File

@@ -1034,12 +1034,12 @@
{/if}
<!-- Map View -->
{#if currentView === 'map' && collection.locations && collection.locations.some((l) => l.latitude && l.longitude)}
{#if currentView === 'map'}
<div class="card bg-base-200 shadow-xl">
<div class="card-body">
<h2 class="card-title text-2xl mb-4">🗺️ Map</h2>
<div class="rounded-lg overflow-hidden shadow-lg">
<CollectionMap {collection} />
<CollectionMap bind:collection user={data.user} />
</div>
</div>
</div>