Skip to content

Commit 9341d3f

Browse files
committed
feat(ui): add clear/reset button to plan new trips
- "Plan New Trip" button appears above Timeline when results are shown - Clears trip result, timeline, and log sheets from view - Resets all form fields to empty via React Hook Form reset() - Clears picked map pins and exits picking mode - Returns map to default US overview (center [39.5, -98.35], zoom 4) - resetKey increments trigger form reset and ResetView map hook - viewResetKey prop added to RouteMap; resetKey prop added to TripInputForm
1 parent 9504401 commit 9341d3f

3 files changed

Lines changed: 44 additions & 1 deletion

File tree

frontend/src/components/RouteMap.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import "leaflet/dist/leaflet.css";
22
import L from "leaflet";
3-
import { useMemo } from "react";
3+
import { useEffect, useMemo } from "react";
44
import {
55
MapContainer,
66
Marker,
@@ -25,6 +25,7 @@ interface RouteMapProps {
2525
pickedLocations?: Partial<Record<LocationType, PickedLocation>>;
2626
onModeChange?: (mode: LocationType | null) => void;
2727
onLocationPicked?: (type: LocationType, lat: number, lng: number) => void;
28+
viewResetKey?: number;
2829
}
2930

3031
const PICKED_COLORS: Record<LocationType, string> = {
@@ -103,6 +104,16 @@ function MapBounds({ points }: { points: [number, number][] }) {
103104
return null;
104105
}
105106

107+
function ResetView({ trigger }: { trigger: number }) {
108+
const map = useMap();
109+
useEffect(() => {
110+
if (trigger > 0) {
111+
map.setView([39.5, -98.35], 4);
112+
}
113+
}, [trigger, map]);
114+
return null;
115+
}
116+
106117
const TYPE_PRIORITY: Record<MarkerType, number> = {
107118
milestone: 3,
108119
stop: 2,
@@ -122,6 +133,7 @@ export function RouteMap({
122133
pickedLocations,
123134
onModeChange,
124135
onLocationPicked,
136+
viewResetKey = 0,
125137
}: RouteMapProps) {
126138
const pickerEnabled = Boolean(onModeChange && onLocationPicked);
127139
const pickedEntries = pickedLocations
@@ -260,6 +272,7 @@ export function RouteMap({
260272
/>
261273
) : null}
262274
<MapBounds points={boundsPoints} />
275+
<ResetView trigger={viewResetKey} />
263276
</MapContainer>
264277
</div>
265278
</div>

frontend/src/components/TripInputForm.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface TripInputFormProps {
1818
isLoading: boolean;
1919
errorMessage?: string;
2020
pickedLocations?: Partial<Record<LocationType, PickedLocation>>;
21+
resetKey?: number;
2122
}
2223

2324
const LOCATION_FIELD: Record<LocationType, keyof FormValues> = {
@@ -31,10 +32,12 @@ export function TripInputForm({
3132
isLoading,
3233
errorMessage,
3334
pickedLocations,
35+
resetKey,
3436
}: TripInputFormProps) {
3537
const {
3638
register,
3739
setValue,
40+
reset,
3841
handleSubmit,
3942
formState: { errors },
4043
} = useForm<FormValues>({
@@ -47,6 +50,12 @@ export function TripInputForm({
4750
},
4851
});
4952

53+
// Reset the form when the parent signals a full clear.
54+
useEffect(() => {
55+
if (!resetKey) return;
56+
reset();
57+
}, [resetKey, reset]);
58+
5059
// Sync picked-label values from the map into the corresponding text input
5160
// so map-only flows still satisfy the form's required-string validation.
5261
useEffect(() => {

frontend/src/pages/PlannerPage.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export function PlannerPage() {
6363
const [pickedLocations, setPickedLocations] = useState<
6464
Partial<Record<LocationType, PickedLocation>>
6565
>({});
66+
const [resetKey, setResetKey] = useState(0);
6667

6768
const mutation = useMutation({
6869
mutationFn: planTrip,
@@ -71,6 +72,14 @@ export function PlannerPage() {
7172

7273
const isLoading = mutation.isPending;
7374

75+
const handleReset = () => {
76+
setResult(null);
77+
setPickedLocations({});
78+
setPickingMode(null);
79+
mutation.reset();
80+
setResetKey((k) => k + 1);
81+
};
82+
7483
const handleLocationPicked = async (type: LocationType, lat: number, lng: number) => {
7584
let label = `${lat.toFixed(4)}, ${lng.toFixed(4)}`;
7685
try {
@@ -125,6 +134,7 @@ export function PlannerPage() {
125134
isLoading={isLoading}
126135
errorMessage={mutation.isError ? parseError(mutation.error) : undefined}
127136
pickedLocations={pickedLocations}
137+
resetKey={resetKey}
128138
/>
129139

130140
{isLoading ? (
@@ -136,6 +146,7 @@ export function PlannerPage() {
136146
pickedLocations={pickedLocations}
137147
onModeChange={setPickingMode}
138148
onLocationPicked={handleLocationPicked}
149+
viewResetKey={resetKey}
139150
/>
140151
)}
141152
</section>
@@ -147,6 +158,16 @@ export function PlannerPage() {
147158
</div>
148159
) : result ? (
149160
<>
161+
<div className="flex items-center justify-between">
162+
<h2 className="text-lg font-semibold text-gray-700">Trip Results</h2>
163+
<button
164+
type="button"
165+
onClick={handleReset}
166+
className="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-600 transition hover:border-red-400 hover:bg-red-50 hover:text-red-600"
167+
>
168+
← Plan New Trip
169+
</button>
170+
</div>
150171
<TimelineView logSheets={result.log_sheets} />
151172
<section className="space-y-6">
152173
{result.log_sheets.map((sheet) => (

0 commit comments

Comments
 (0)