Skip to content

Commit d1b5aeb

Browse files
feat: Implement Advanced Log Filtering and UI/UX Improvements (#259)
* feat(map): Enhance traffic polyline visibility with casing Adds a wider, semi-transparent black "casing" polyline that is rendered underneath the primary colored traffic line on the map. This creates a border effect that significantly improves the visibility and contrast of the planned route, especially on complex map backgrounds or when zoomed out. * feat(filters): Implement advanced log filtering by type and trip ID Introduces a new filtering system to allow users to narrow down large log datasets. - Adds a filter bar to the UI with a dropdown for log types (for On Demand Trips) and a text input for filtering by a partial or full Trip ID. - The core filtering logic is centralized in `TripLogs.getLogs_`. - All data-driven components (LogTable, Map, TimeSlider) and navigation controls now respect the active filters. * feat(map): Better trip colors that are more distinct from traffic colors * fix: Remove spammy debug log * fix: linter 46fe54f
1 parent 78a488d commit d1b5aeb

File tree

11 files changed

+384
-108
lines changed

11 files changed

+384
-108
lines changed

src/App.js

Lines changed: 145 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,78 @@ const MARKER_COLORS = [
3636
"#26A69A", // Darker Teal
3737
];
3838

39+
const ODRD_FILTERS = ["createVehicle", "getVehicle", "updateVehicle", "createTrip", "getTrip", "updateTrip"];
40+
41+
function FilterBar({ availableFilters, filterState, clickHandler }) {
42+
const [isOpen, setIsOpen] = React.useState(false);
43+
const filterBarRef = React.useRef(null);
44+
45+
React.useEffect(() => {
46+
function handleClickOutside(event) {
47+
if (filterBarRef.current && !filterBarRef.current.contains(event.target)) {
48+
log("Clicked outside FilterBar, closing menu.");
49+
setIsOpen(false);
50+
}
51+
}
52+
if (isOpen) {
53+
log("Filter menu is open, adding click outside listener.");
54+
document.addEventListener("mousedown", handleClickOutside);
55+
}
56+
return () => {
57+
document.removeEventListener("mousedown", handleClickOutside);
58+
};
59+
}, [isOpen]);
60+
61+
if (!availableFilters || availableFilters.length === 0) {
62+
return null;
63+
}
64+
65+
const handleButtonClick = () => {
66+
log(`Filter button clicked. Menu will be ${!isOpen ? "opened" : "closed"}.`);
67+
setIsOpen(!isOpen);
68+
};
69+
70+
const activeFilterCount = availableFilters.filter((type) => filterState[type]).length;
71+
const totalFilterCount = availableFilters.length;
72+
const buttonText = `Filter Log Types (${activeFilterCount}/${totalFilterCount})`;
73+
const isFiltered = activeFilterCount !== totalFilterCount;
74+
75+
return (
76+
<div style={{ position: "relative", display: "inline-block" }} ref={filterBarRef}>
77+
<button
78+
className={`toggle-button ${isOpen || isFiltered ? "toggle-button-active" : ""}`}
79+
onClick={handleButtonClick}
80+
>
81+
{buttonText}
82+
</button>
83+
{isOpen && (
84+
<div className="filter-menu">
85+
{availableFilters.map((filterType) => (
86+
<label key={filterType} className="filter-menu-item">
87+
<input type="checkbox" checked={!!filterState[filterType]} onChange={() => clickHandler(filterType)} />
88+
{filterType}
89+
</label>
90+
))}
91+
</div>
92+
)}
93+
</div>
94+
);
95+
}
96+
97+
function TripIdFilter({ value, onChange }) {
98+
return (
99+
<div style={{ marginLeft: "10px" }}>
100+
<input
101+
type="text"
102+
placeholder="Filter by Trip ID"
103+
value={value}
104+
onChange={onChange}
105+
className="trip-id-filter-input"
106+
/>
107+
</div>
108+
);
109+
}
110+
39111
class App extends React.Component {
40112
constructor(props) {
41113
super(props);
@@ -55,6 +127,21 @@ class App extends React.Component {
55127
featuredObject: { msg: "Click a table row to select object" },
56128
extraColumns: [],
57129
toggleOptions: Object.fromEntries(ALL_TOGGLES.map((t) => [t.id, false])),
130+
logTypeFilters: {
131+
createVehicle: true,
132+
getVehicle: true,
133+
updateVehicle: true,
134+
createTrip: true,
135+
getTrip: true,
136+
updateTrip: true,
137+
createDeliveryVehicle: true,
138+
getDeliveryVehicle: true,
139+
updateDeliveryVehicle: true,
140+
createTask: true,
141+
getTask: true,
142+
updateTask: true,
143+
},
144+
tripIdFilter: "",
58145
uploadedDatasets: [null, null, null, null, null],
59146
activeDatasetIndex: null,
60147
activeMenuIndex: null,
@@ -176,7 +263,9 @@ class App extends React.Component {
176263
this.setState((prevState) => {
177264
const minDate = new Date(prevState.timeRange.minTime);
178265
const maxDate = new Date(prevState.timeRange.maxTime);
179-
const logs = this.state.currentLogData.tripLogs.getLogs_(minDate, maxDate).value();
266+
const logs = this.state.currentLogData.tripLogs
267+
.getLogs_(minDate, maxDate, prevState.logTypeFilters, prevState.tripIdFilter)
268+
.value();
180269
if (logs.length > 0) {
181270
const firstRow = logs[0];
182271
setTimeout(() => this.focusOnSelectedRow(), 0);
@@ -194,7 +283,12 @@ class App extends React.Component {
194283
selectLastRow = () => {
195284
const minDate = new Date(this.state.timeRange.minTime);
196285
const maxDate = new Date(this.state.timeRange.maxTime);
197-
const logsWrapper = this.state.currentLogData.tripLogs.getLogs_(minDate, maxDate);
286+
const logsWrapper = this.state.currentLogData.tripLogs.getLogs_(
287+
minDate,
288+
maxDate,
289+
this.state.logTypeFilters,
290+
this.state.tripIdFilter
291+
);
198292
const logs = logsWrapper.value();
199293
if (logs.length > 0) {
200294
const lastRow = logs[logs.length - 1];
@@ -206,17 +300,25 @@ class App extends React.Component {
206300
};
207301

208302
handleRowChange = async (direction) => {
209-
const { featuredObject } = this.state;
303+
const { featuredObject, logTypeFilters, tripIdFilter } = this.state;
210304
const minDate = new Date(this.state.timeRange.minTime);
211305
const maxDate = new Date(this.state.timeRange.maxTime);
212-
const logs = this.state.currentLogData.tripLogs.getLogs_(minDate, maxDate).value();
306+
const logs = this.state.currentLogData.tripLogs.getLogs_(minDate, maxDate, logTypeFilters, tripIdFilter).value();
213307
let newFeaturedObject = featuredObject;
214308
const currentIndex = logs.findIndex((log) => log.timestamp === featuredObject.timestamp);
215-
if (direction === "next" && currentIndex < logs.length - 1) {
309+
310+
if (currentIndex === -1) {
311+
if (logs.length > 0) {
312+
newFeaturedObject = logs[0];
313+
} else {
314+
return;
315+
}
316+
} else if (direction === "next" && currentIndex < logs.length - 1) {
216317
newFeaturedObject = logs[currentIndex + 1];
217318
} else if (direction === "previous" && currentIndex > 0) {
218319
newFeaturedObject = logs[currentIndex - 1];
219320
}
321+
220322
if (newFeaturedObject !== featuredObject) {
221323
this.setState({ featuredObject: newFeaturedObject }, () => {
222324
this.focusOnSelectedRow();
@@ -789,6 +891,22 @@ class App extends React.Component {
789891
this.updateToggleState(newValue, id, toggle.columns);
790892
}
791893

894+
handleLogTypeFilterChange = (filterType) => {
895+
log(`Filter toggled for: ${filterType}`);
896+
this.setState((prevState) => ({
897+
logTypeFilters: {
898+
...prevState.logTypeFilters,
899+
[filterType]: !prevState.logTypeFilters[filterType],
900+
},
901+
}));
902+
};
903+
904+
handleTripIdFilterChange = (event) => {
905+
const value = event.target.value;
906+
log(`Trip ID filter changed to: "${value}"`);
907+
this.setState({ tripIdFilter: value });
908+
};
909+
792910
render() {
793911
const {
794912
featuredObject,
@@ -798,8 +916,11 @@ class App extends React.Component {
798916
extraColumns,
799917
dynamicMarkerLocations,
800918
visibleToggles,
919+
logTypeFilters,
920+
tripIdFilter,
801921
} = this.state;
802922
const selectedEventTime = featuredObject?.timestamp ? new Date(featuredObject.timestamp).getTime() : null;
923+
const availableFilters = currentLogData.solutionType === "ODRD" ? ODRD_FILTERS : [];
803924

804925
return (
805926
<div className="app-container">
@@ -821,6 +942,8 @@ class App extends React.Component {
821942
setRenderMarkerOnMap={this.setRenderMarkerOnMap}
822943
focusSelectedRow={this.focusOnSelectedRow}
823944
initialMapBounds={this.state.initialMapBounds}
945+
logTypeFilters={logTypeFilters}
946+
tripIdFilter={tripIdFilter}
824947
/>
825948
</div>
826949
<TimeSlider
@@ -832,12 +955,22 @@ class App extends React.Component {
832955
onRowSelect={(row, rowIndex) => this.onSelectionChange(row, rowIndex)}
833956
centerOnLocation={this.centerOnLocation}
834957
focusSelectedRow={this.focusOnSelectedRow}
958+
logTypeFilters={logTypeFilters}
959+
tripIdFilter={tripIdFilter}
835960
/>
836-
<ToggleBar
837-
toggles={visibleToggles}
838-
toggleState={toggleOptions}
839-
clickHandler={(id) => this.toggleClickHandler(id)}
840-
/>
961+
<div className="togglebar-button-group">
962+
<ToggleBar
963+
toggles={visibleToggles}
964+
toggleState={toggleOptions}
965+
clickHandler={(id) => this.toggleClickHandler(id)}
966+
/>
967+
<FilterBar
968+
availableFilters={availableFilters}
969+
filterState={logTypeFilters}
970+
clickHandler={this.handleLogTypeFilterChange}
971+
/>
972+
<TripIdFilter value={tripIdFilter} onChange={this.handleTripIdFilterChange} />
973+
</div>
841974
<div className="nav-controls">
842975
<div className="button-row">
843976
<div className="playback-controls">
@@ -883,6 +1016,8 @@ class App extends React.Component {
8831016
onSelectionChange={(rowData, rowIndex) => this.onSelectionChange(rowData, rowIndex)}
8841017
setFocusOnRowFunction={this.setFocusOnRowFunction}
8851018
centerOnLocation={this.centerOnLocation}
1019+
logTypeFilters={logTypeFilters}
1020+
tripIdFilter={tripIdFilter}
8861021
/>
8871022
</div>
8881023
</div>

src/Dataframe.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ function Dataframe({ featuredObject, extraColumns, onColumnToggle, onToggleMarke
153153
const CustomLocationOperation = useCallback(
154154
({ node }) => {
155155
if (isLocationObject(node)) {
156-
log("Dataframe: Rendering location button for node.");
157156
const locationKey = `${node.latitude}_${node.longitude}`;
158157
const markerState = dynamicMarkerLocations[locationKey];
159158
const color = markerState ? markerState.color : "lightgrey";

src/LogTable.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ function LogTable(props) {
165165
const [selectedRowIndex, setSelectedRowIndex] = useState(-1);
166166
const minTime = props.timeRange.minTime;
167167
const maxTime = props.timeRange.maxTime;
168-
const data = props.logData.tripLogs.getLogs_(new Date(minTime), new Date(maxTime)).value();
168+
const data = props.logData.tripLogs.getLogs_(new Date(minTime), new Date(maxTime), props.filters).value();
169169
const columnShortWidth = 50;
170170
const columnRegularWidth = 120;
171171
const columnLargeWidth = 150;
@@ -221,17 +221,17 @@ function LogTable(props) {
221221
solutionTypes: ["ODRD", "LMFS"],
222222
},
223223
{
224-
Header: "TripId 7",
224+
Header: "CurrentTrip12",
225225
accessor: (entry) => {
226226
const currentTrips = _.get(entry, "response.currenttrips");
227227
if (currentTrips && currentTrips[0]) {
228228
const tripId = currentTrips[0];
229-
return tripId.substring(Math.max(0, tripId.length - 7));
229+
return tripId.substring(Math.max(0, tripId.length - 12));
230230
}
231231
return null;
232232
},
233-
width: columnShortWidth,
234-
maxWidth: columnShortWidth,
233+
width: 90,
234+
maxWidth: 90,
235235
className: "logtable-cell",
236236
solutionTypes: ["ODRD"],
237237
},

src/Map.js

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ function MapComponent({
2323
initialMapBounds,
2424
setCenterOnLocation,
2525
setRenderMarkerOnMap,
26+
filters,
2627
}) {
2728
const { tripLogs, taskLogs, jwt, projectId, mapId } = logData;
2829

@@ -172,21 +173,48 @@ function MapComponent({
172173
};
173174
map.controls[window.google.maps.ControlPosition.LEFT_BOTTOM].push(followButton);
174175

176+
const centerListener = map.addListener(
177+
"center_changed",
178+
_.debounce(() => {
179+
if (mapRef.current) setQueryStringValue("center", JSON.stringify(mapRef.current.getCenter().toJSON()));
180+
}, 100)
181+
);
182+
const headingListener = map.addListener("heading_changed", () => {
183+
if (mapRef.current) setQueryStringValue("heading", mapRef.current.getHeading());
184+
});
185+
186+
return () => {
187+
dynamicMarkersRef.current.forEach((m) => m.setMap(null));
188+
dynamicMarkersRef.current = [];
189+
Object.values(vehicleMarkersRef.current).forEach((marker) => marker && marker.setMap(null));
190+
vehicleMarkersRef.current = {};
191+
window.google.maps.event.removeListener(centerListener);
192+
window.google.maps.event.removeListener(headingListener);
193+
mapRef.current = null;
194+
};
195+
}, []);
196+
197+
// Effect to manage the map click listener
198+
useEffect(() => {
199+
const map = mapRef.current;
200+
if (!map) return;
201+
log("Map.js: Attaching map click listener.");
202+
175203
const clickListener = map.addListener("click", (event) => {
176204
log("Map click listener triggered.");
177205
const clickLocation = event.latLng;
178-
const logs = tripLogs.getLogs_(new Date(rangeStart), new Date(rangeEnd)).value();
206+
const logs = tripLogs.getLogs_(new Date(rangeStart), new Date(rangeEnd), filters).value();
179207
let closestEvent = null;
180-
let closestDistance = 250;
208+
let searchDistanceMeters = 250;
181209

182210
logs.forEach((logEntry) => {
183-
const rawLocation = _.get(logEntry, "lastlocation.rawlocation");
211+
const rawLocation = _.get(logEntry, "lastlocation.location");
184212
if (rawLocation?.latitude && rawLocation?.longitude) {
185213
const eventLocation = new window.google.maps.LatLng(rawLocation.latitude, rawLocation.longitude);
186214
const distance = window.google.maps.geometry.spherical.computeDistanceBetween(clickLocation, eventLocation);
187-
if (distance < closestDistance) {
215+
if (distance < searchDistanceMeters) {
188216
closestEvent = logEntry;
189-
closestDistance = distance;
217+
searchDistanceMeters = distance;
190218
}
191219
}
192220
});
@@ -196,28 +224,10 @@ function MapComponent({
196224
}
197225
});
198226

199-
const centerListener = map.addListener(
200-
"center_changed",
201-
_.debounce(() => {
202-
if (mapRef.current) setQueryStringValue("center", JSON.stringify(mapRef.current.getCenter().toJSON()));
203-
}, 100)
204-
);
205-
const headingListener = map.addListener("heading_changed", () => {
206-
if (mapRef.current) setQueryStringValue("heading", mapRef.current.getHeading());
207-
});
208-
209227
return () => {
210-
log("Map.js: Cleanup from initialization useEffect.");
211-
dynamicMarkersRef.current.forEach((m) => m.setMap(null));
212-
dynamicMarkersRef.current = [];
213-
Object.values(vehicleMarkersRef.current).forEach((marker) => marker && marker.setMap(null));
214-
vehicleMarkersRef.current = {};
215228
window.google.maps.event.removeListener(clickListener);
216-
window.google.maps.event.removeListener(centerListener);
217-
window.google.maps.event.removeListener(headingListener);
218-
mapRef.current = null;
219229
};
220-
}, []);
230+
}, [mapRef, tripLogs, rangeStart, rangeEnd, filters, setFeaturedObject, focusSelectedRow]);
221231

222232
const handlePolylineSubmit = useCallback((waypoints, properties) => {
223233
const map = mapRef.current;

src/TimeSlider.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ function TimeSlider(props) {
7777
log(`TimeSlider - Finding closest event to: ${new Date(clickTime).toISOString()}`);
7878

7979
// Find the closest event to this time
80-
const logs = tripLogs.getLogs_(new Date(minVal), new Date(maxVal)).value();
80+
const logs = tripLogs.getLogs_(new Date(minVal), new Date(maxVal), props.filters).value();
8181

8282
if (logs.length > 0) {
8383
// Find closest log entry to the clicked time
@@ -182,7 +182,7 @@ function TimeSlider(props) {
182182
document.removeEventListener("mouseup", handleMouseUp);
183183
document.removeEventListener("touchend", handleMouseUp, { passive: true });
184184
};
185-
}, [minVal, maxVal, tripLogs, isDragging, props.onRowSelect, props.centerOnLocation]);
185+
}, [minVal, maxVal, tripLogs, isDragging, props.onRowSelect, props.centerOnLocation, props.filters]);
186186

187187
return (
188188
<div style={style} ref={sliderContainerRef}>

0 commit comments

Comments
 (0)