Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0f27bf7
Adding Wastewater card
tlsharkey Jul 28, 2025
e691e8d
Adding spanish version of wastewater card
tlsharkey Jul 28, 2025
836ad4f
Making wastewater card autoupdate values when language changes
tlsharkey Jul 28, 2025
102c220
Merge remote-tracking branch 'origin/main' into feature/wastewater-card
tlsharkey Jul 28, 2025
506cdd1
#42 air quality complaints
valentinedwv Aug 15, 2025
09e3f06
#42 air quality complaints.
valentinedwv Aug 15, 2025
56f3da5
Merge pull request #40 from TheResilientCollective/feature/wastewater…
valentinedwv Aug 15, 2025
8f9e6c1
Merge pull request #43 from TheResilientCollective/fix/renamesmell
valentinedwv Aug 15, 2025
8669b1e
Merge pull request #44 from TheResilientCollective/develop
valentinedwv Aug 15, 2025
839e91b
#42 air quality complaints.
valentinedwv Aug 15, 2025
3a47d29
Merge pull request #46 from TheResilientCollective/develop
valentinedwv Aug 15, 2025
9a94fee
Add 'No Wastwater Flows' if there are no flows
valentinedwv Aug 16, 2025
0f99dae
no-wastewater-flows indicator with language support, html order, miss…
tlsharkey Aug 26, 2025
cc27980
Displayed wastewater and complaint data shows n-days from last update
tlsharkey Sep 16, 2025
e124b30
Updating date range label on complaints and flows
tlsharkey Sep 16, 2025
d856833
Merge pull request #48 from TheResilientCollective/hotfix/render-side…
tlsharkey Sep 16, 2025
a3d43f5
Merge pull request #49 from TheResilientCollective/develop
tlsharkey Sep 16, 2025
a424d40
Fixing spill days query parameter parsing
tlsharkey Sep 16, 2025
ede7b46
Merge remote-tracking branch 'origin/develop' into hotfix/spills-quer…
tlsharkey Sep 16, 2025
10b22a8
Unifies how spill/complaint date ranges are calculated
tlsharkey Sep 16, 2025
c6bd656
Fixing card subheadings for complaints/spills
tlsharkey Sep 16, 2025
3612893
Merge pull request #50 from TheResilientCollective/hotfix/spills-quer…
tlsharkey Sep 17, 2025
13e1d14
Merge pull request #51 from TheResilientCollective/develop
tlsharkey Sep 17, 2025
4b2b0ef
Merge remote-tracking branch 'origin/release/tjdashboard'
tlsharkey Sep 17, 2025
35f571a
Merge pull request #53 from TheResilientCollective/hotfix/52-merge
tlsharkey Sep 17, 2025
171f83e
Merge pull request #54 from TheResilientCollective/develop
tlsharkey Sep 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions html_js/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,11 @@ button.button.map-control-button.button {
transition: color 0.2s;
}

.card .sublabel {
text-align: left;
padding-left: calc(var(--p1) + var(--icon));
}

.card-data table {
width: 100%;
table-layout: fixed;
Expand Down Expand Up @@ -939,4 +944,8 @@ body:fullscreen #fullscreen-btn {

body:-webkit-full-screen #fullscreen-btn {
display: none;
}

.hidden {
display: none;
}
45 changes: 36 additions & 9 deletions html_js/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,35 @@
const params = new URLSearchParams(window.location.search);

// Get the value of the "id" parameter
const complaint_days = params.get('complaint')?? 14;
const complaint_days = Number(params.get('complaint')?? 14);
window.complaint_days = complaint_days;
window.complaint_timeframe = {
numDays: complaint_days,
latestDate: undefined,
getMostRecentSampleTime: (latestDate=undefined) => {
if (!latestDate) latestDate = window.complaint_timeframe.latestDate;
return new Date(latestDate.getTime() - (window.complaint_timeframe.numDays) * 24 * 60 * 60 * 1000);
},
getSecondMostRecentSampleTime: (latestDate=undefined) => {
if (!latestDate) latestDate = window.complaint_timeframe.latestDate;
return new Date(latestDate.getTime() - (window.complaint_timeframe.numDays*2) * 24 * 60 * 60 * 1000);
},
}
// Get the value of the "name" parameter
const spill_days = params.get('spill')?? 30;
const spill_days = Number(params.get('spill')?? 30);
window.spill_days = spill_days;
window.spill_timeframe = {
numDays: spill_days,
latestDate: undefined,
getMostRecentSampleTime: (latestDate=undefined) => {
if (!latestDate) latestDate = window.spill_timeframe.latestDate;
return new Date(latestDate.getTime() - (window.spill_timeframe.numDays) * 24 * 60 * 60 * 1000);
},
getSecondMostRecentSampleTime: (latestDate=undefined) => {
if (!latestDate) latestDate = window.spill_timeframe.latestDate;
return new Date(latestDate.getTime() - (window.spill_timeframe.numDays*2) * 24 * 60 * 60 * 1000);
},
}
console.log('complaint', complaint_days)
console.log('spills', spill_days)
// <button onclick="toggleLayer(this, 'Beach Closures')">Beach Closures</button>
Expand Down Expand Up @@ -221,7 +245,7 @@
<div>
<span data-i18n="topbar.odorComplaints">Smell Complaints</span>
<br>
<span class="trend-flat sublabel" data-i18n="topbar.prevComplaintdays" '>previous 3 days</span>
<span class="trend-flat sublabel" data-i18n="topbar.prevComplaintdays"></span>
</div>
</button>
<button id="spills-filter-btn" class="active" onclick="toggleLayer(this, 'Wastewater Spills')">
Expand All @@ -230,7 +254,7 @@
<div>
<span data-i18n="topbar.wastewaterSpills">Wastewater Spills</span>
<br>
<span class="trend-flat sublabel" data-i18n="topbar.prevSpilldays">previous 30 days</span>
<span class="trend-flat sublabel" data-i18n="topbar.prevSpilldays"></span>
</div>
</button>
<!-- <button id="river-filter-btn" class="" onclick="toggleLayer(this, 'River Basin')">-->
Expand Down Expand Up @@ -258,7 +282,7 @@

<div id="sidebar-header">
<div><a href="https://www.sandiegocounty.gov/" target="_blank"><img src="img/logoCOSD.png"></a></div>
<div id="sidebar-title"><h1 data-i18n="sidebar.title">Tijuana River Valley Sewage Crisis Environmental Dashboard</span></div>
<div id="sidebar-title"><h1 data-i18n="sidebar.title">Tijuana River Valley Sewage Crisis Environmental Dashboard</h1></div>
<div><a href="https://resilientshield.ucsd.edu/" target="_blank"><img src="img/ResilientLogo.png"></a></div>
</div>

Expand Down Expand Up @@ -349,7 +373,7 @@ <h3 class="card-title" data-i18n="sidebar.cards.whatYouCanDo.title">What You Can
<li data-i18n="sidebar.cards.whatYouCanDo.infoItems.source">The name and address of the alleged source and the type of operation causing it (if known)</li>
</ul>
</div>

<br>
<p>
<span data-i18n="sidebar.cards.whatYouCanDo.h2sLink.label2" data-i18n-html="true">.</span>
Expand Down Expand Up @@ -631,24 +655,27 @@ <h3 class="card-title" data-i18n="sidebar.cards.wastewater.title">Public Wastewa
<span data-i18n="sidebar.cards.wastewater.p1.body">The following are public complaints about wastewater that have been reported to SDAPCD.</span>
<br>
<a href="https://example.com" target="_blank" data-i18n="sidebar.cards.wastewater.p1.link.text" data-i18n-href="sidebar.cards.wastewater.p1.link.url">More Info</a>
<br>

</p>
<p>
<span data-i18n="sidebar.cards.wastewater.p2.body">and here</span>
<br>
<a href="https://example.com" target="_blank" data-i18n="sidebar.cards.wastewater.p2.link.text" data-i18n-href="sidebar.cards.wastewater.p2.link.url">Wastewater Spills</a>

</p>

<div id="wastewater-data" class="card-data">
<span data-i18n="sidebar.cards.wastewater.tableLabel">Complaints in the last 7 days</span>
<table>
<tbody>
<tr>

<p id="no-wastewater-flows" class="hidden" data-i18n="sidebar.cards.wastewater.noFlows">No Wastewater Flows</p>
</tr>
</tbody>
</table>
</div>

<div class="card-footer">

<span data-i18n="sidebar.cards.wastewater.footer.text"></span>
<a target="_blank" href="https://example.com" data-i18n="sidebar.cards.wastewater.footer.link.text" data-i18n-href="sidebar.cards.wastewater.footer.link.url">More Info</a>
</div>
Expand Down
70 changes: 60 additions & 10 deletions html_js/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ function renderOdorComplaints(geoData) {
window.latestOdorData = geoData;

const now = new Date();
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const mostRecentSampleTime = new Date(now.getTime() - (window.complaint_days) * 24 * 60 * 60 * 1000);
const secondMostRecentSampleTime = new Date(now.getTime() - (window.complaint_days*2) * 24 * 60 * 60 * 1000);
const latestDate = window.complaint_timeframe.latestDate;
const mostRecentSampleTime = window.complaint_timeframe.getMostRecentSampleTime();
const secondMostRecentSampleTime = window.complaint_timeframe.getSecondMostRecentSampleTime();

console.log(
"[app.js] Current date:",
Expand Down Expand Up @@ -205,6 +205,17 @@ function renderOdorComplaints(geoData) {
console.log("[app.js] Updated trend indicator with key:", trendKey);
}

// Table label with date range
const tableLabel = document.querySelector("[data-i18n='sidebar.cards.odorComplaints.tableLabel']");
if (tableLabel) {
tableLabel.innerText = i18next.t("sidebar.cards.odorComplaints.tableLabel", {
start_date: mostRecentSampleTime.toLocaleDateString(i18next.language || "en", { month: 'short', day: 'numeric' }),
end_date: latestDate.toLocaleDateString(i18next.language || "en", { month: 'short', day: 'numeric' }),
complaint_days: window.complaint_days
});
}

// clear old data from table
const jsonDiv = document.querySelector("#odor-complaint-data tbody");
if (!jsonDiv) {
console.warn("[app.js] Odor complaint data table not found.");
Expand All @@ -218,6 +229,7 @@ function renderOdorComplaints(geoData) {
});
console.log("[app.js] Grouped odor complaints by day:", mostRecentDataByDay);

// build table rows
for (const day in mostRecentDataByDay) {
console.log("[app.js] Adding row for odor complaint:", day, "with", mostRecentDataByDay[day].length, "complaints");
const rowElm = document.createElement("tr");
Expand Down Expand Up @@ -255,7 +267,6 @@ function renderOdorComplaints(geoData) {
"#odor-complaints-card .card-footer"
);
if (cardFooter && mostRecentData.length > 0) {
latestDate = dayjs(geoData.lastUpdated).toDate();
console.log("[app.js] Updating odor complaints footer with latest date.", geoData.lastUpdated, "converted to", latestDate);
const span = cardFooter.querySelector("span");
const formattedDate = formatDateTime(latestDate, {
Expand All @@ -279,9 +290,9 @@ function renderWastewaterFlows(data) {
window.latestWastewaterData = data;

const now = new Date();
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const mostRecentSampleTime = new Date(now.getTime() - (window.spill_days) * 24 * 60 * 60 * 1000);
const secondMostRecentSampleTime = new Date(now.getTime() - (window.spill_days*2) * 24 * 60 * 60 * 1000);
const latestDate = window.spill_timeframe.latestDate;
const mostRecentSampleTime = window.spill_timeframe.getMostRecentSampleTime()
const secondMostRecentSampleTime = window.spill_timeframe.getSecondMostRecentSampleTime();

console.log(
"[app.js] (Spills) Current date:",
Expand Down Expand Up @@ -364,6 +375,15 @@ function renderWastewaterFlows(data) {
console.log("[app.js] (Spills) Updated trend indicator with key:", trendKey);
}

const tableLabel = document.querySelector("[data-i18n='sidebar.cards.wastewater.tableLabel']");
if (tableLabel) {
tableLabel.innerText = i18next.t("sidebar.cards.wastewater.tableLabel", {
start_date: mostRecentSampleTime.toLocaleDateString(i18next.language || "en", { month: 'short', day: 'numeric' }),
end_date: latestDate.toLocaleDateString(i18next.language || "en", { month: 'short', day: 'numeric' }),
spill_days: window.spill_days
});
}

// Table
const jsonDiv = document.querySelector("#wastewater-data tbody");
if (!jsonDiv) {
Expand All @@ -389,17 +409,19 @@ function renderWastewaterFlows(data) {
<td><i class="bi bi-clock"></i><span>${dateRange}</span></td>
</tr>`;
const rowElm = new DOMParser().parseFromString(template, "text/html").body.firstChild;

// jsonDiv.appendChild(rowElm);
jsonDiv.innerHTML += template;
console.log("[app.js] (Spills) Added spill entry:", { startTime, endTime, volume, notes }, jsonDiv.lastChild);
}

if (mostRecentData.length === 0 ){
document.querySelector("#no-wastewater-flows").classList.remove("hidden");
console.log("[app.js] (Spills) No data found.");
}
const cardFooter = document.querySelector(
"#wastewater-card .card-footer"
);
if (cardFooter) {
latestDate = dayjs(data.lastUpdated).toDate();
console.log("[app.js] (Spills) Updating wastewater footer with latest date.", data.lastUpdated, "converted to", latestDate);
const span = cardFooter.querySelector("span");
const formattedDate = formatDateTime(latestDate, {
Expand Down Expand Up @@ -573,6 +595,20 @@ function fetchOdorData() {
.then((response) =>
response.ok ? response.json() : Promise.reject(response.statusText)
)
.then((jsonData) => {
window.complaint_timeframe.latestDate = dayjs(jsonData.lastUpdated).toDate();
document.querySelector("[data-i18n='topbar.prevComplaintdays']").setAttribute("data-i18n-options", JSON.stringify({
start_date: window.complaint_timeframe.latestDate.toLocaleDateString(i18next.language || "en", { month: 'short', day: 'numeric' }),
end_date: window.complaint_timeframe.getMostRecentSampleTime().toLocaleDateString(i18next.language || "en", { month: 'short', day: 'numeric' }),
}));
document.querySelector('[data-i18n="sidebar.cards.odorComplaints.overview.sampleDuration"').setAttribute("data-i18n-options", JSON.stringify({
start_date: window.complaint_timeframe.getMostRecentSampleTime().toLocaleDateString(i18next.language || "en", { month: 'short', day: 'numeric' }),
end_date: window.complaint_timeframe.latestDate.toLocaleDateString(i18next.language || "en", { month: 'short', day: 'numeric' }),
complaint_days: window.complaint_days
}));
updateContent();
return jsonData;
})
.then((jsonData) => {
renderOdorComplaints(jsonData);
})
Expand Down Expand Up @@ -605,6 +641,20 @@ function fetchWastewaterData() {
.then((response) =>
response.ok ? response.json() : Promise.reject(response.statusText)
)
.then(async (jsonData) => {
window.spill_timeframe.latestDate = dayjs(jsonData.lastUpdated).toDate();
document.querySelector("[data-i18n='topbar.prevSpilldays']").setAttribute("data-i18n-options", JSON.stringify({
start_date: window.spill_timeframe.latestDate.toLocaleDateString(i18next.language || "en", { month: 'short', day: 'numeric' }),
end_date: window.spill_timeframe.getMostRecentSampleTime().toLocaleDateString(i18next.language || "en", { month: 'short', day: 'numeric' }),
}));
document.querySelector('[data-i18n="sidebar.cards.wastewater.overview.sampleDuration"').setAttribute("data-i18n-options", JSON.stringify({
start_date: window.spill_timeframe.getMostRecentSampleTime().toLocaleDateString(i18next.language || "en", { month: 'short', day: 'numeric' }),
end_date: window.spill_timeframe.latestDate.toLocaleDateString(i18next.language || "en", { month: 'short', day: 'numeric' }),
spill_days: window.spill_days
}));
updateContent();
return jsonData;
})
.then((jsonData) => {
renderWastewaterFlows(jsonData);
})
Expand Down
32 changes: 23 additions & 9 deletions html_js/js/mbmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function complaints_layer(complaint_days) {
try {
fetch(`${urlbase}tijuana/sd_complaints/output/latest/complaints.geojson`) // update the path or URL to your GeoJSON file
.then((response) => response.json())
.then((data) => {
.then(async (data) => {
const complaintsVisible = document
.querySelector("#complaints-filter-btn")
.classList.contains("active");
Expand All @@ -71,13 +71,19 @@ function complaints_layer(complaint_days) {
clusterMaxZoom: 14, // max zoom to cluster points
clusterRadius: 50, // radius of each cluster when clustering points (in pixels)
});
const threeDaysAgo = dayjs().subtract(complaint_days, "day");
console.log("days ago date: ", threeDaysAgo);

while (!window.complaint_timeframe.latestDate) {
await new Promise((resolve) => setTimeout(resolve, 500));
}
const latestDate = window.complaint_timeframe.latestDate;
const mostRecentSampleTime = window.complaint_timeframe.getMostRecentSampleTime();
const useDataSinceDate = dayjs(mostRecentSampleTime);
console.log("[mbmap.js] days ago date: ", useDataSinceDate);
// Filter features where the "Date Recieved" is within the last three days.
// Adjust the date parsing if your format differs.
var filteredFeatures = data.features.filter((feature) => {
var dateReceived = new Date(feature.properties["date_received"]);
return dateReceived >= threeDaysAgo;
return dateReceived >= useDataSinceDate;
});
console.log("complaints after filter: ", filteredFeatures.length);

Expand Down Expand Up @@ -486,7 +492,7 @@ ${
}
}

function spills_layer(spill_days) {
async function spills_layer(spill_days) {
// spills
try {
const spillVisible = document
Expand All @@ -497,7 +503,15 @@ function spills_layer(spill_days) {
type: "geojson",
data: `${urlbase}tijuana/ibwc/output/spills_last_by_site.geojson`,
});
const thirtyDaysAgo = dayjs().subtract(spill_days, "day").toISOString();

while (!window.spill_timeframe.latestDate) {
await new Promise((resolve) => setTimeout(resolve, 500));
}

const latestDate = window.spill_timeframe.latestDate;
const mostRecentSampleTime = window.spill_timeframe.getMostRecentSampleTime();
const useDataSinceDate = dayjs(mostRecentSampleTime).toISOString();
console.log("[mbmap.js] spill days ago date: ", useDataSinceDate);
// Create a symbol layer using the custom pin icon
map.addLayer({
id: "spills",
Expand All @@ -506,10 +520,10 @@ function spills_layer(spill_days) {
filter: ["any",
[
"any",
[">=", ["get", "End Time"], thirtyDaysAgo],
[">=", ["get", "Start Time"], thirtyDaysAgo],
[">=", ["get", "End Time"], useDataSinceDate],
[">=", ["get", "Start Time"], useDataSinceDate],
] ,
[">=", ["get", "Start Time"], thirtyDaysAgo], // in case End Time 'Ongoing' is a different value
[">=", ["get", "Start Time"], useDataSinceDate], // in case End Time 'Ongoing' is a different value
],


Expand Down
13 changes: 7 additions & 6 deletions html_js/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@
"count": "loading Complaints",
"count_one": "{{count}} Complaint",
"count_other": "{{count}} Complaints",
"sampleDuration": "in the last 14 days",
"sampleDuration": "since {{start_date}}",
"trend": "-{{change}} from 2 weeks ago",
"trend_positive": "+{{change}} from 2 weeks ago",
"trend_same": "same as 2 weeks ago"
Expand All @@ -151,7 +151,7 @@
"url": "https://www.sdapcd.org/content/sdapcd/compliance/air-quality-complaints.html"
}
},
"tableLabel": "Complaints in the last {{complaint_days}} days",
"tableLabel": "Complaints from {{start_date}} to {{end_date}}",
"dailyCount_one": "{{count}} complaint",
"dailyCount_other": "{{count}} complaints",
"footer": {
Expand All @@ -168,7 +168,7 @@
"count": "loading flows",
"count_one": "{{count}} Flow",
"count_other": "{{count}} Flows",
"sampleDuration": "in the last {{spill_days}} days",
"sampleDuration": "since {{start_date}}",
"trend": "-{{change}} from {{spill_days}} days ago",
"trend_positive": "+{{change}} from {{spill_days}} days ago",
"trend_same": "same as {{spill_days}} days ago"
Expand All @@ -187,9 +187,10 @@
"url": "https://waterdata.ibwc.gov/AQWebportal/Data/Dashboard/8"
}
},
"tableLabel": "Flows in the last {{spill_days}} days",
"tableLabel": "Flows from {{start_date}} to {{end_date}}",
"dailyCount_one": "{{count}} flow",
"dailyCount_other": "{{count}} flows",
"noFlows": "No Wastewater Flows",
"footer": {
"text": "Last Updated on {{date}}",
"link": {
Expand Down Expand Up @@ -225,8 +226,8 @@
"wastewaterSpills": "Wastewater Flows",
"riverBasin": "River Basin",
"currentData": "current data",
"prevComplaintdays": "previous {{complaint_days}} days",
"prevSpilldays": "previous {{spill_days}} days"
"prevComplaintdays": "from {{start_date}} to {{end_date}}",
"prevSpilldays": "from {{start_date}} to {{end_date}}"
},
"mapFooter": {
"disclaimer": {
Expand Down
Loading