Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions html_js/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,19 @@ button.button.map-control-button.button {
float: right;
}

#spills-count {
font-weight: normal;
}

#wastewater-data tr td:last-child {
width: 55%;
}

#wastewater-data tr td img {
width: var(--icon);
height: var(--icon);
}

.sublabel {
font-size: 0.75rem !important;
width: 100%;
Expand Down
53 changes: 53 additions & 0 deletions html_js/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,59 @@ <h3 class="card-title" data-i18n="sidebar.cards.odorComplaints.title">Public Odo
</div>
</div>

<!-- Card: Wastewater -->
<div id="wastewater-card" class="card shadow expandable" onclick="selectCard('Wastewater')">
<div class="card-header always-expand">
<img src="img/marker-spill-outline.svg">
<h3 class="card-title" data-i18n="sidebar.cards.wastewater.title">Public Wastewater Complaints</h3>
<button class="expand-btn"><i class="bi bi-chevron-down"></i></button>
</div>

<div id="wastewater-data-overview" class="card-data always-expand">
<table>
<tbody>
<tr>
<td>
<span class="indicator moderate"></span>
<span id="spills-count" data-i18n="sidebar.cards.wastewater.overview.count">-3 Beaches Closed</span>
<br><span class="sublabel trend-flat" data-i18n="sidebar.cards.wastewater.overview.sampleDuration">in the last 7 days</span>
</td>
<td>
<i class="bi bi-graph-up-arrow trend-up"></i>
<span class="trend-up" data-i18n="sidebar.cards.wastewater.overview.trend"> +100 from last week</span>
</td>
</tr>
</tbody>
</table>
</div>

<p>
<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>
<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>

</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>
</div>

<div class="sidebar-card-separator"><span><h2 data-i18n="sidebar.sections.health">Health Concerns</h2></span></div>

<!-- Gastrointestinal -->
Expand Down
161 changes: 161 additions & 0 deletions html_js/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,151 @@ function renderOdorComplaints(geoData) {
}
}

function renderWastewaterFlows(data) {
console.log("[app.js] (Spills) Rendering Wastewater Flows with data:", 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);

console.log(
"[app.js] (Spills) Current date:",
now,
"most recent sample:",
mostRecentSampleTime,
"second most recent sample:",
secondMostRecentSampleTime
);

let mostRecentData = data.features.filter((item) => {
let itemDate = new Date(item.properties["End Time"]);
return itemDate.getTime() >= mostRecentSampleTime.getTime()
});
console.log(`[app.js] (Spills) Last ${window.spill_days} days data:`, mostRecentData);

mostRecentData = _.orderBy(
mostRecentData,
[(item) => new Date(item.properties["Start Time"]).getTime()],
["desc"]
);

let secondMostRecentData = data.features.filter((item) => {
let itemDate = new Date(item.properties["End Time"]);
return itemDate.getTime() >= secondMostRecentSampleTime.getTime() && itemDate.getTime() < mostRecentSampleTime.getTime()
});
console.log(`[app.js] (Spills) Previous ${window.spill_days} days data:`, secondMostRecentData);

const sumMostRecent = mostRecentData.length;
const sumSecondMostRecent = secondMostRecentData.length;
console.log(
`[app.js] (Spills) Sum of spills in last ${window.spill_days} days:`,
sumMostRecent,
`Previous ${window.spill_days} days:`,
sumSecondMostRecent
);

const countSpan = document.getElementById("spills-count");
const countIndicator = countSpan?.parentElement.querySelector(".indicator");
if (countSpan && countIndicator) {
countSpan.innerText = i18next.t(
"sidebar.cards.wastewater.overview.count",
{ count: sumMostRecent }
);
countIndicator.className =
"indicator " + (sumMostRecent > 0 ? "high" : "low");
console.log("[app.js] (Spills) Updated spills count and indicator.", countSpan.innerText, sumMostRecent);
}

const countSpillsChange = sumMostRecent - sumSecondMostRecent;
console.log("[app.js] (Spills) Change in spills:", countSpillsChange);

const changeContainer = document.querySelector(
"#wastewater-data-overview tr td:last-child"
);
if (changeContainer) {
const changeSpan = changeContainer.querySelector("span");
const changeIcon = changeContainer.querySelector("i");
let trendKey = "sidebar.cards.wastewater.overview.trend";
let trendClass = "trend-down";
let iconClass = "bi-graph-up-arrow";

if (countSpillsChange === 0) {
trendKey = "sidebar.cards.wastewater.overview.trend_same";
trendClass = "trend-flat";
iconClass = "";
} else if (countSpillsChange < 0) {
trendClass = "trend-up";
iconClass = "bi-graph-down-arrow";
} else {
trendKey = "sidebar.cards.wastewater.overview.trend_positive";
}

changeSpan.innerText = i18next.t(trendKey, {
change: Math.abs(countSpillsChange),
spill_days: window.spill_days
});
changeIcon.className = `bi ${iconClass}`;
changeSpan.className = trendClass;
console.log("[app.js] (Spills) Updated trend indicator with key:", trendKey);
}

// Table
const jsonDiv = document.querySelector("#wastewater-data tbody");
if (!jsonDiv) {
console.warn("[app.js] (Spills) Wastewater data table not found.");
return;
}
jsonDiv.innerHTML = "";
console.log("[app.js] (Spills) Clearing old data and building new table.", jsonDiv);
console.log("[app.js] (Spills) Most recent data:", mostRecentData);

for (const spill of mostRecentData) {
console.log("[app.js] (Spills) Adding spill entry:", spill);
const startTime = new Date(spill.properties["Start Time"]);
const endTime = new Date(spill.properties["End Time"]);
const singleDayEvent = startTime.toDateString() === endTime.toDateString();
const dateRange = singleDayEvent ? formatDateTime(startTime) : `${formatDateTime(startTime)} to ${formatDateTime(endTime)}`;
const volume = spill.properties["Approximate Discharge Volume"];
const notes = spill.properties["Notes"];

const template = `
<tr class="card-data">
<td><img src="img/marker-spill-outline.svg"><span>${volume}</span></td>
<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);
}

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, {
month: "long",
day: "numeric",
hour: "numeric",
hour12: true,
});
span.innerText = i18next.t("sidebar.cards.wastewater.footer.text", {
date: formattedDate,
});
console.log(
"[app.js] (Spills) Updated wastewater footer with date:",
formattedDate
);
}

}

// --- Beach Closures Rendering ---
function renderBeachClosures(jsonData) {
latestBeachData = jsonData; // Store data
Expand Down Expand Up @@ -453,6 +598,22 @@ function fetchBeachData() {
});
}

function fetchWastewaterData() {
fetch(
`${resilientUrlBase}tijuana/ibwc/output/spills_last_by_site.geojson`
)
.then((response) =>
response.ok ? response.json() : Promise.reject(response.statusText)
)
.then((jsonData) => {
renderWastewaterFlows(jsonData);
})
.catch((error) => {
console.error("Error fetching Wastewater Flows JSON:", error);
document.querySelector("#wastewater-flows-card").remove();
});
}

function getIndicatorLevelForOdorComplaints(count, accumulatedOverDays = 1) {
// FIXME: Tommy just made up these numbers
if (count < 3 * accumulatedOverDays) {
Expand Down
5 changes: 5 additions & 0 deletions html_js/js/language.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ async function initializeI18next() {
// --- Fetch initial data AFTER i18next is ready ---
fetchH2SData();
fetchOdorData();
fetchWastewaterData();
fetchBeachData();

// --- Optional: Language Switcher ---
Expand Down Expand Up @@ -107,6 +108,10 @@ function updateContent() {
console.log("[language.js] Re-rendering Beach Closures");
renderBeachClosures(window.latestBeachData);
}
if (typeof renderWastewaterFlows === "function" && window.latestWastewaterData) {
console.log("[language.js] Re-rendering Wastewater Flows");
renderWastewaterFlows(window.latestWastewaterData);
}

// Close any tooltips
try {
Expand Down
44 changes: 40 additions & 4 deletions html_js/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"h2sLink": {
"label1": "Recommendations to ",
"link1": {
"text": "Protect Yourself from Environmental Odors",
"text": "Protect Yourself from Environmental Gases",
"url": "https://www.sandiegocounty.gov/content/sdc/hhsa/programs/phs/community_epidemiology/south-region-health-concerns/about-environmental-odors.html"
},
"airpurifiers": "San Diego County Air Pollution Control District (SDAPCD) is providing free air purifiers to residents living in affected communities. Check with the <a href=\"https://www.sdapcd.org/content/sdapcd/about/tj-river-valley/aire.html\" target=\"_blank\">AIRE Program</a> to see if you are eligible",
Expand Down Expand Up @@ -134,7 +134,7 @@
}
},
"odorComplaints": {
"title": "Smell Complaints",
"title": "Air Quality Complaints",
"overview": {
"count": "loading Complaints",
"count_one": "{{count}} Complaint",
Expand Down Expand Up @@ -162,6 +162,42 @@
}
}
},
"wastewater": {
"title": "Wastewater Flows",
"overview": {
"count": "loading flows",
"count_one": "{{count}} Flow",
"count_other": "{{count}} Flows",
"sampleDuration": "in the last {{spill_days}} days",
"trend": "-{{change}} from {{spill_days}} days ago",
"trend_positive": "+{{change}} from {{spill_days}} days ago",
"trend_same": "same as {{spill_days}} days ago"
},
"p1": {
"body": "The following are wastewater events in the Tijuana River Valley. Events include Spills, Dry Weather Canyon Collector Transboundary Flows, and Tijuana River Transboundary Flows. Rain events are not included in this data. More information on these events can be found here:",
"link": {
"text": "San Diego Regional Water Board",
"url": "https://www.waterboards.ca.gov/sandiego/water_issues/programs/tijuana_river_valley_strategy/sewage_issue.html"
}
},
"p2": {
"body": "Detailed data can be found here:",
"link": {
"text": "International Boundary & Water Commission - Tijuana River Basin",
"url": "https://waterdata.ibwc.gov/AQWebportal/Data/Dashboard/8"
}
},
"tableLabel": "Flows in the last {{spill_days}} days",
"dailyCount_one": "{{count}} flow",
"dailyCount_other": "{{count}} flows",
"footer": {
"text": "Last Updated on {{date}}",
"link": {
"text": "",
"url": ""
}
}
},
"healthConcerns": {
"title": "Public Health Data",
"p1": {
Expand All @@ -185,7 +221,7 @@
"topbar": {
"beaches": "Beach Water Quality",
"h2sLevels": "H₂S Levels",
"odorComplaints": "Smell Complaints",
"odorComplaints": "Air Quality Complaints",
"wastewaterSpills": "Wastewater Flows",
"riverBasin": "River Basin",
"currentData": "current data",
Expand All @@ -200,7 +236,7 @@
"legend": {
"label": "Legend ",
"spill": "Wastewater flow",
"complaint": "Smell complaint",
"complaint": "Air Quality Complaint",
"h2s": "H2S reading",
"outlet": "Outfall",
"beach": "Beach"
Expand Down
Loading