diff --git a/client/src/screens/TravelScreen.tsx b/client/src/screens/TravelScreen.tsx index c63f3b9..eb83663 100644 --- a/client/src/screens/TravelScreen.tsx +++ b/client/src/screens/TravelScreen.tsx @@ -1,32 +1,63 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { api } from '../api/api'; import { ApiError } from '../api/http'; -import type { City } from '../api/types'; +import type { City, WorldEvent } from '../api/types'; import { useAuth } from '../auth/AuthContext'; import { useToast } from '../components/Toast'; import { formatMoney, riskLabel } from '../shared/format'; +/** Sagoma stilizzata dell'Italia (coordinate 0-100, stesse di City.mapX/mapY). */ +const ITALY_MAINLAND = + 'M12,18 L18,11 L30,9 L44,8 L50,13 L54,20 L60,28 L70,40 L82,46 L91,52 L88,57 L79,58 ' + + 'L71,63 L65,71 L63,79 L58,76 L57,67 L52,58 L45,46 L37,34 L25,27 L15,24 Z'; +const ITALY_SICILY = 'M44,83 L54,81 L62,84 L58,91 L48,93 L43,88 Z'; +const ITALY_SARDINIA = 'M13,55 L21,56 L22,66 L19,74 L12,72 L11,62 Z'; + +const RISK_COLORS: Record = { + basso: '#4caf6e', + medio: '#d4a017', + alto: '#d9534f', +}; + export function TravelScreen() { const { player, setPlayer } = useAuth(); const { toast } = useToast(); const [cities, setCities] = useState([]); + const [events, setEvents] = useState([]); + const [selected, setSelected] = useState(null); const [busy, setBusy] = useState(false); const reload = useCallback(async () => { - const res = await api.cities(); - setCities(res.cities); + const [c, e] = await Promise.all([api.cities(), api.events()]); + setCities(c.cities); + setEvents(e.events); }, []); useEffect(() => { void reload(); }, [reload]); + const eventsByCity = useMemo(() => { + const map = new Map(); + for (const event of events) { + const key = event.city?.id ?? 'global'; + map.set(key, [...(map.get(key) ?? []), event]); + } + return map; + }, [events]); + + const cityEvents = (cityId: string): WorldEvent[] => [ + ...(eventsByCity.get(cityId) ?? []), + ...(eventsByCity.get('global') ?? []), + ]; + async function travelTo(city: City) { setBusy(true); try { const res = await api.travel(city.id); setPlayer(res.player); toast(`Benvenuto a ${res.cityName} (−${formatMoney(res.cost)})`, 'success'); + setSelected(null); } catch (err) { toast(err instanceof ApiError ? err.message : 'Errore imprevisto', 'error'); } finally { @@ -36,34 +67,101 @@ export function TravelScreen() { return (
-

Città

+

Mappa

+ +
+ + + + + + {cities.map((city) => { + const isCurrent = player?.currentCityId === city.id; + const hasEvent = (eventsByCity.get(city.id) ?? []).length > 0; + const color = RISK_COLORS[riskLabel(city.riskLevel)] ?? '#d4a017'; + return ( + setSelected(city)} + transform={`translate(${city.mapX}, ${city.mapY})`} + > + {isCurrent && } + + + {hasEvent && ( + + ⚡ + + )} + + {city.name} + + + ); + })} + +

+ ● colore = rischio (verde basso, oro medio, rosso alto) · ⚡ evento in corso · tocca una + città per viaggiare +

+
+
    {cities.map((city) => { const isCurrent = player?.currentCityId === city.id; - const canAfford = (player?.money ?? 0) >= city.travelCost; return ( -
  • +
  • setSelected(city)} + >
    {city.name} {isCurrent && sei qui} -

    - rischio {riskLabel(city.riskLevel)} · economia ×{city.economyModifier} -

    + {(eventsByCity.get(city.id) ?? []).length > 0 && }
    - {!isCurrent && ( - - )} + + {isCurrent ? '—' : formatMoney(city.travelCost)} +
  • ); })}
+ + {selected && ( +
setSelected(null)}> +
e.stopPropagation()}> +

{selected.name}

+

+ rischio {riskLabel(selected.riskLevel)} · economia ×{selected.economyModifier} · + polizia ×{selected.policePressure} +

+ {cityEvents(selected.id).length > 0 && ( +
+ {cityEvents(selected.id).map((event) => ( + + ⚡ {event.title} + {!event.city && ' (globale)'} + + ))} +
+ )} + {player?.currentCityId === selected.id ? ( +

Ti trovi già in questa città.

+ ) : ( + + )} +
+
+ )}
); }