Bootssymbol hinzugefügt
Test and publish container images / test (push) Successful in 2m20s
Test and publish container images / publish (push) Failing after 4s

This commit is contained in:
BuTzZ
2026-07-26 12:31:49 +02:00
parent c9a20aacd7
commit 55ce94066e
19 changed files with 1969 additions and 122 deletions
+77 -73
View File
@@ -23,6 +23,7 @@ import {
buildVoyagePlan,
VOYAGE_AMENITIES,
voyageAmenityLabel,
type BoatProfile,
type Coordinate,
type NavigationDataSnapshot,
type RouteResult,
@@ -32,6 +33,11 @@ import {
} from "@watermaps/shared";
import type { RouteWeatherReport } from "../routeWeatherReport";
import type { OfflineVoyage } from "../lib/offline-route";
import {
boatCategoryLabel,
DEFAULT_BOAT_PROFILE,
toVesselProfile
} from "../lib/boat-profile";
import type { RouteLock } from "../voyageHarbours";
import { LazyContent } from "./LazyContent";
import { NavigationDataPanel } from "./NavigationDataPanel";
@@ -58,6 +64,8 @@ type RoutePlannerProps = {
routeTides?: RouteTidePlan | null;
routeTidesLoading?: boolean;
routeTidesError?: string | null;
boatProfile?: BoatProfile;
routeVesselProfile?: VesselProfile | null;
loading: boolean;
error: string | null;
pickMode: "start" | "destination" | "waypoint" | null;
@@ -66,7 +74,6 @@ type RoutePlannerProps = {
destination: Coordinate;
waypoints: Coordinate[];
departureTime: string;
vesselProfile: VesselProfile;
}) => void;
onPickStart: () => void;
onPickDestination: () => void;
@@ -80,6 +87,7 @@ type RoutePlannerProps = {
onSelectRoute: (routeId: string) => void;
guidanceActive?: boolean;
onStartGuidance?: () => void;
onEditBoat?: () => void;
operationalPanelsVisible?: boolean;
embedded?: boolean;
onCollapse: () => void;
@@ -113,6 +121,8 @@ export function RoutePlanner({
routeTides = null,
routeTidesLoading = false,
routeTidesError = null,
boatProfile = DEFAULT_BOAT_PROFILE,
routeVesselProfile = null,
loading,
error,
pickMode,
@@ -129,15 +139,11 @@ export function RoutePlanner({
onSelectRoute,
guidanceActive = false,
onStartGuidance = () => undefined,
onEditBoat = () => undefined,
operationalPanelsVisible = true,
embedded = false,
onCollapse
}: RoutePlannerProps) {
const [draughtM, setDraughtM] = useState(1.4);
const [reserveM, setReserveM] = useState(0.5);
const [airDraftM, setAirDraftM] = useState(2.5);
const [beamM, setBeamM] = useState(3.2);
const [speedKn, setSpeedKn] = useState(6);
const [departureTime, setDepartureTime] = useState(() => localDateTimeValue(new Date()));
const [maxCruisingHoursPerDay, setMaxCruisingHoursPerDay] = useState(8);
const [requiredAmenities, setRequiredAmenities] = useState<VoyageAmenity[]>(["overnight"]);
@@ -149,6 +155,16 @@ export function RoutePlanner({
const [voyageToolsRequested, setVoyageToolsRequested] = useState(false);
const [sheetState, setSheetState] = useState<RouteSheetState>("half");
const previousResult = useRef(result);
const vesselProfile = useMemo(() => toVesselProfile(boatProfile), [boatProfile]);
const planningVesselProfile = result && routeVesselProfile
? routeVesselProfile
: vesselProfile;
const speedKn = planningVesselProfile.cruiseSpeedKn ?? boatProfile.cruiseSpeedKn;
const routeProfileDiffers = Boolean(
result &&
routeVesselProfile &&
!sameVesselProfile(routeVesselProfile, vesselProfile)
);
const canRoute = Boolean(startPoint && destination);
const isPickingStart = pickMode === "start";
const isPickingDestination = pickMode === "destination";
@@ -387,7 +403,7 @@ export function RoutePlanner({
open={routeOptionsOpen}
onToggle={(event) => setRouteOptionsOpen(event.currentTarget.open)}
>
<summary>Routenoptionen: Boot · Zwischenziele</summary>
<summary>Routenoptionen: Zwischenziele</summary>
<section className="waypoint-editor" aria-label="Zwischenziele">
<div className="waypoint-editor-heading">
<strong>Zwischenziele</strong>
@@ -446,65 +462,35 @@ export function RoutePlanner({
)}
</section>
<div className="route-grid">
<label>
Tiefgang (m)
<input
value={draughtM}
min={0.1}
max={15}
step={0.1}
type="number"
onChange={(event) => setDraughtM(Number(event.target.value))}
/>
</label>
<label>
Sicherheitsreserve (m)
<input
value={reserveM}
min={0}
max={10}
step={0.1}
type="number"
onChange={(event) => setReserveM(Number(event.target.value))}
/>
</label>
<label>
Durchfahrtshöhe (m)
<input
value={airDraftM}
min={0.5}
max={40}
step={0.1}
type="number"
onChange={(event) => setAirDraftM(Number(event.target.value))}
/>
</label>
<label>
Geschwindigkeit (kn)
<input
value={speedKn}
min={1}
max={80}
step={0.5}
type="number"
onChange={(event) => setSpeedKn(Number(event.target.value))}
/>
</label>
<label>
Breite (m)
<input
value={beamM}
min={0.5}
max={40}
step={0.1}
type="number"
onChange={(event) => setBeamM(Number(event.target.value))}
/>
</label>
</div>
</details>
<section
className="current-boat-summary"
aria-label={`Aktuelles Boot: ${boatProfile.name}`}
data-route-profile-differs={routeProfileDiffers}
>
<span className="current-boat-summary-icon">
<ShipWheel size={18} aria-hidden="true" />
</span>
<span>
<strong>{boatProfile.name}</strong>
<small>
{boatCategoryLabel(boatProfile.category)} · {formatProfileMeters(boatProfile.lengthM)}
{" × "}
{formatProfileMeters(boatProfile.beamM)} · Tiefgang{" "}
{formatProfileMeters(boatProfile.draughtM)}
</small>
{routeProfileDiffers && (
<em role="status">
Diese geladene Route wurde mit einem anderen Bootsprofil berechnet.
</em>
)}
</span>
<button className="secondary-action compact-action" type="button" onClick={onEditBoat}>
Bearbeiten
</button>
</section>
<label className="departure-planning">
<span>
<Clock3 size={14} aria-hidden="true" />
@@ -529,8 +515,7 @@ export function RoutePlanner({
start: startPoint,
destination,
waypoints,
departureTime: toIsoOrNow(departureTime),
vesselProfile: { draughtM, safetyReserveM: reserveM, airDraftM, beamM, cruiseSpeedKn: speedKn }
departureTime: toIsoOrNow(departureTime)
});
}}
>
@@ -582,6 +567,17 @@ export function RoutePlanner({
)}
</div>
{routeProfileDiffers && (
<section className="warning-list" aria-label="Abweichendes Bootsprofil" role="alert">
<strong>Route gehört zu einem anderen Bootsprofil</strong>
<p>
<AlertTriangle size={14} aria-hidden="true" />
Die geladene Offline-Route behält ihre damaligen Maße. Für {boatProfile.name} muss
sie mit dem aktuellen Bootsprofil neu berechnet werden.
</p>
</section>
)}
{(error || criticalWarnings.length > 0) && (
<section
className="warning-list route-priority-warnings"
@@ -913,13 +909,7 @@ export function RoutePlanner({
start: startPoint,
destination,
waypoints,
vesselProfile: {
draughtM,
safetyReserveM: reserveM,
airDraftM,
beamM,
cruiseSpeedKn: speedKn
},
vesselProfile: planningVesselProfile,
departureAt: result?.departureTime ?? toIsoOrNow(departureTime)
}
: undefined
@@ -979,6 +969,20 @@ function formatMeters(value: number | null | undefined) {
return typeof value === "number" ? `${value.toFixed(1)} m` : "-- m";
}
function formatProfileMeters(value: number) {
return `${value.toLocaleString("de-DE", { maximumFractionDigits: 2 })} m`;
}
function sameVesselProfile(left: VesselProfile, right: VesselProfile) {
return (
left.draughtM === right.draughtM &&
left.safetyReserveM === right.safetyReserveM &&
left.airDraftM === right.airDraftM &&
left.beamM === right.beamM &&
left.cruiseSpeedKn === right.cruiseSpeedKn
);
}
function formatBridgeMargin(value: number | null | undefined) {
if (typeof value !== "number") {
return "offen";