Skip to content

Commit 8757383

Browse files
authored
Merge pull request #49 from UTSC-CSCC01-Software-Engineering-I/fix/bugs_features
Fix/bugs features
2 parents a9f0f26 + 0bb3110 commit 8757383

4 files changed

Lines changed: 249 additions & 20 deletions

File tree

backend/src/models/UserPoint.js

Whitespace-only changes.

frontend/src/components/HUDleftPoints.jsx

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ function LogoBlock() {
1919
const [filteredList, setFilteredList] = useState([]);
2020
const [sortOrder, setSortOrder] = useState(null);
2121
const [showSortMenu, setShowSortMenu] = useState(false);
22-
const [unit, setUnit] = useState(window.temperatureUnit || 'C');
22+
// Fix: Initialize with 'C' and update in useEffect
23+
const [unit, setUnit] = useState('C');
2324
const [showFilterModal, setShowFilterModal] = useState(false);
2425
const [tempFilter, setTempFilter] = useState({ min: '', max: '' });
2526

@@ -152,15 +153,27 @@ const resetTempFilter = () => {
152153
setShowSuggestions(false);
153154
};
154155

156+
// Update the useEffect that handles unit changes
155157
useEffect(() => {
156158
const handleUnitChange = () => {
157-
setUnit(window.temperatureUnit || 'C');
159+
// Check if window exists before accessing it
160+
if (typeof window !== 'undefined') {
161+
setUnit(window.temperatureUnit || 'C');
162+
}
158163
};
159-
window.addEventListener('unitchange', handleUnitChange);
160-
// initialize on mount
164+
165+
// Initialize on mount
161166
handleUnitChange();
167+
168+
// Add event listener
169+
if (typeof window !== 'undefined') {
170+
window.addEventListener('unitchange', handleUnitChange);
171+
}
172+
162173
return () => {
163-
window.removeEventListener('unitchange', handleUnitChange);
174+
if (typeof window !== 'undefined') {
175+
window.removeEventListener('unitchange', handleUnitChange);
176+
}
164177
};
165178
}, []);
166179

@@ -435,7 +448,8 @@ const resetTempFilter = () => {
435448
lineHeight: 1.4,
436449
transition: 'color 0.3s ease'
437450
}}>
438-
{item.siteName}
451+
{/* Display "User Point" for user-generated points, otherwise use siteName */}
452+
{item.isUserPoint ? 'User Point' : (item.siteName || 'User Point')}
439453
</h2>
440454
<p style={{
441455
fontSize: '0.75rem',
@@ -445,14 +459,25 @@ const resetTempFilter = () => {
445459
: 'rgba(0,0,0,0.6)'
446460
}}>
447461
{ item.timestamp
448-
? new Date(
449-
// drop the "Z" so this is parsed as local midnight
450-
item.timestamp.replace(' ', 'T')
451-
).toLocaleDateString('en-US', {
452-
day: 'numeric',
453-
month: 'short',
454-
year: 'numeric'
455-
})
462+
? (() => {
463+
// Handle different timestamp formats
464+
let date;
465+
if (item.isUserPoint) {
466+
// For user points, timestamp is from updatedAt (ISO string)
467+
date = new Date(item.timestamp);
468+
} else {
469+
// For official data, timestamp might be in different format
470+
date = new Date(item.timestamp.replace(' ', 'T'));
471+
}
472+
473+
return date.toLocaleDateString('en-US', {
474+
day: 'numeric',
475+
month: 'short',
476+
year: 'numeric',
477+
hour: item.isUserPoint ? '2-digit' : undefined,
478+
minute: item.isUserPoint ? '2-digit' : undefined
479+
});
480+
})()
456481
: '—' }
457482
</p>
458483
</div>

frontend/src/components/MapComponent.jsx

Lines changed: 121 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,42 @@ export default function MapComponent() {
8080
return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;
8181
}
8282

83+
// Add this function for better contrast calculation
84+
function getAccessibleTemperatureColor(temp, unit = 'C', mode = 'light') {
85+
const tempC = unit === 'F' ? (temp - 32) * 5 / 9 : parseFloat(temp);
86+
87+
const min = 0;
88+
const max = 30;
89+
const ratio = Math.min(1, Math.max(0, (tempC - min) / (max - min)));
90+
91+
const hue = 270 - ratio * 270; // purple → red
92+
const saturation = 90; // Slightly reduced for better readability
93+
94+
// Ensure sufficient contrast for WCAG AA compliance
95+
const lightness = mode === 'dark' ? 45 : 35; // Darker colors for better contrast
96+
const alpha = 0.9; // Higher opacity for better visibility
97+
98+
return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;
99+
}
100+
101+
// Add this function to get temperature category for screen readers
102+
function getTemperatureCategory(tempC) {
103+
if (tempC < 5) return 'Very Cold';
104+
if (tempC < 15) return 'Cold';
105+
if (tempC < 20) return 'Cool';
106+
if (tempC < 25) return 'Warm';
107+
return 'Hot';
108+
}
109+
110+
// Add this function to get temperature icon/symbol
111+
function getTemperatureSymbol(tempC) {
112+
if (tempC < 5) return '🧊'; // ice
113+
if (tempC < 15) return '❄️'; // snowflake
114+
if (tempC < 20) return '🌊'; // wave
115+
if (tempC < 25) return '🏊'; // swimmer
116+
return '🔥'; // fire
117+
}
118+
83119
useEffect(() => {
84120
if (typeof window !== 'undefined') {
85121
window.loadedAPI = loading;
@@ -127,6 +163,7 @@ export default function MapComponent() {
127163
const map = L.map(mapRef.current, {
128164
center: [43.647216678117736, -79.36719310664458],
129165
zoom: 12,
166+
zoomControl: false,
130167
layers: [isDarkTheme ? darkLayer : lightLayer]
131168
});
132169

@@ -150,6 +187,7 @@ export default function MapComponent() {
150187
// Store the cleanup function for later use
151188
map._themeCleanup = removeThemeListener;
152189

190+
// Update the addMarkers function
153191
function addMarkers(items) {
154192
items.forEach((item, i) => {
155193
const lon = item.lng || item.lon || item.Longitude;
@@ -160,24 +198,71 @@ export default function MapComponent() {
160198
// Get current unit and format temperature
161199
const currentUnit = UnitManager.getUnit();
162200
const formattedTemp = formatTemperature(t, currentUnit);
163-
const tempColor = getTemperatureColor(t, 'C'); // Always calculate color from Celsius
201+
const tempColor = getAccessibleTemperatureColor(t, 'C'); // Use accessible color function
202+
const tempCategory = getTemperatureCategory(t);
203+
const tempSymbol = getTemperatureSymbol(t);
164204

165205
console.log(`Plotting [${i}]: ${name} @ ${lat},${lon} = ${formattedTemp}`);
166206

167207
const icon = L.divIcon({
168208
className: 'custom-temp-marker',
169-
html: `<div class="temp-label" style="background-color: ${tempColor};">${formattedTemp}</div>`,
170-
iconSize: [40, 40],
171-
iconAnchor: [20, 20],
209+
html: `
210+
<div
211+
class="temp-label accessible-marker"
212+
style="background-color: ${tempColor};"
213+
role="button"
214+
tabindex="0"
215+
aria-label="Water temperature ${formattedTemp} at ${name}. Category: ${tempCategory}. Press Enter or Space to view details."
216+
data-temp-category="${tempCategory.toLowerCase().replace(' ', '-')}"
217+
>
218+
<span class="temp-value">${formattedTemp}</span>
219+
</div>
220+
`,
221+
iconSize: [50, 40], // Slightly larger for better touch targets
222+
iconAnchor: [25, 20],
172223
});
173224

174225
const marker = L.marker([lat, lon], { icon }).addTo(map);
175226

227+
// Add function to announce to screen readers (this was missing)
228+
function announceToScreenReader(message) {
229+
const announcement = document.createElement('div');
230+
announcement.setAttribute('aria-live', 'polite');
231+
announcement.setAttribute('aria-atomic', 'true');
232+
announcement.className = 'sr-only';
233+
announcement.textContent = message;
234+
235+
document.body.appendChild(announcement);
236+
237+
// Remove after announcement
238+
setTimeout(() => {
239+
if (document.body.contains(announcement)) {
240+
document.body.removeChild(announcement);
241+
}
242+
}, 1000);
243+
}
244+
245+
// Add click handler FIRST (this is the main functionality)
176246
marker.on('click', async () => {
247+
console.log('Marker clicked:', name); // Debug log
248+
249+
// Announce to screen readers
250+
try {
251+
announceToScreenReader(`Viewing details for ${name} with water temperature ${formattedTemp}`);
252+
} catch (e) {
253+
console.log('Screen reader announcement failed:', e);
254+
}
255+
177256
const historicalData = await fetchHistoricalData(name);
178257

179258
if (historicalData.length === 0) {
180-
marker.bindPopup(`<strong>${name}</strong><br/>No historical data available`).openPopup();
259+
marker.bindPopup(`
260+
<div role="dialog" aria-labelledby="popup-title-${i}">
261+
<h3 id="popup-title-${i}">${name}</h3>
262+
<p>No historical data available</p>
263+
<p>Current temperature: ${formattedTemp} (${tempCategory})</p>
264+
</div>
265+
`).openPopup();
181266
return;
182267
}
183268

@@ -375,6 +460,37 @@ export default function MapComponent() {
375460
}, 100);
376461
});
377462

463+
// Add keyboard accessibility AFTER the click handler
464+
marker.on('add', () => {
465+
// Small delay to ensure DOM is ready
466+
setTimeout(() => {
467+
const markerElement = marker.getElement();
468+
if (markerElement) {
469+
const tempLabel = markerElement.querySelector('.temp-label');
470+
if (tempLabel) {
471+
// Add keyboard event listeners
472+
tempLabel.addEventListener('keydown', (e) => {
473+
if (e.key === 'Enter' || e.key === ' ') {
474+
e.preventDefault();
475+
console.log('Keyboard trigger for:', name); // Debug log
476+
marker.fire('click'); // This should trigger the click handler
477+
}
478+
});
479+
480+
// Add focus styling
481+
tempLabel.addEventListener('focus', () => {
482+
tempLabel.style.outline = '3px solid #0066cc';
483+
tempLabel.style.outlineOffset = '2px';
484+
});
485+
486+
tempLabel.addEventListener('blur', () => {
487+
tempLabel.style.outline = 'none';
488+
});
489+
}
490+
}
491+
}, 100);
492+
});
493+
378494
markersRef.current.push({ marker, tempC: t, name, lat, lon });
379495
});
380496
}

frontend/src/styles/MapView.css

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
/* 🟢 Thin black outline around text */
4141
text-shadow: 0 0 2px black;
42+
/* -webkit-text-stroke: 0.5px black; */
4243
}
4344
/* Gradient Legend (temperature scale) */
4445
.legend {
@@ -79,4 +80,91 @@
7980
justify-content: space-between;
8081
height: 200px; /* match bar height */
8182
}
82-
83+
/* Add these accessible styles */
84+
.accessible-marker {
85+
display: flex !important;
86+
align-items: center !important;
87+
gap: 0.25rem !important;
88+
min-height: 44px !important; /* WCAG minimum touch target */
89+
min-width: 44px !important;
90+
cursor: pointer !important;
91+
transition: all 0.2s ease !important;
92+
}
93+
94+
.accessible-marker:hover,
95+
.accessible-marker:focus {
96+
transform: scale(1.1) !important;
97+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important;
98+
outline: 3px solid #0066cc !important;
99+
outline-offset: 2px !important;
100+
}
101+
102+
.accessible-marker:focus-visible {
103+
outline: 3px solid #0066cc !important;
104+
outline-offset: 2px !important;
105+
}
106+
107+
.temp-symbol {
108+
font-size: 1rem !important;
109+
margin-right: 0.2rem !important;
110+
}
111+
112+
.temp-value {
113+
font-weight: bold !important;
114+
font-size: 1rem !important;
115+
}
116+
117+
/* Screen reader only content */
118+
.sr-only {
119+
position: absolute !important;
120+
width: 1px !important;
121+
height: 1px !important;
122+
padding: 0 !important;
123+
margin: -1px !important;
124+
overflow: hidden !important;
125+
clip: rect(0, 0, 0, 0) !important;
126+
white-space: nowrap !important;
127+
border: 0 !important;
128+
}
129+
130+
/* High contrast mode support */
131+
@media (prefers-contrast: high) {
132+
.temp-label {
133+
border: 2px solid currentColor !important;
134+
background-color: ButtonFace !important;
135+
color: ButtonText !important;
136+
}
137+
}
138+
139+
/* Reduced motion support */
140+
@media (prefers-reduced-motion: reduce) {
141+
.accessible-marker {
142+
transition: none !important;
143+
}
144+
145+
.accessible-marker:hover,
146+
.accessible-marker:focus {
147+
transform: none !important;
148+
}
149+
}
150+
151+
/* Temperature category styles for additional visual context */
152+
.temp-label[data-temp-category="very-cold"] {
153+
border-left: 4px solid #0066cc !important;
154+
}
155+
156+
.temp-label[data-temp-category="cold"] {
157+
border-left: 4px solid #4d79a6 !important;
158+
}
159+
160+
.temp-label[data-temp-category="cool"] {
161+
border-left: 4px solid #7ea6d1 !important;
162+
}
163+
164+
.temp-label[data-temp-category="warm"] {
165+
border-left: 4px solid #ff9500 !important;
166+
}
167+
168+
.temp-label[data-temp-category="hot"] {
169+
border-left: 4px solid #ff3b30 !important;
170+
}

0 commit comments

Comments
 (0)