Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement air quality alerts in styled forecasts view #76

Merged
Merged
51 changes: 50 additions & 1 deletion app/assets/stylesheets/application.tailwind.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,54 @@
@tailwind utilities;

.active {
@apply border-[3px] border-black border-solid border-b-0 text-black -mb-[3px] bg-white;
@apply border-[3px] border-black border-solid border-b-0 -mb-[3px] bg-white;
}

.after-today {
@apply text-black bg-white;
}

.daqi-level-1-today,
.daqi-level-2-today,
.daqi-level-3-today {
@apply text-black bg-white;
}

.inactive {
@apply border-b-0 border-l-2 border-r-2 border-t-2 border-gray-400 border-dashed text-gray-400;
}

.daqi-level-4-today,
.daqi-alert-after-today-selected-level-4 {
@apply text-black bg-yellow-300;
}

.daqi-level-5-today,
.daqi-alert-after-today-selected-level-5 {
@apply text-black bg-amber-200;
}

.daqi-level-6-today,
.daqi-alert-after-today-selected-level-6 {
@apply text-black bg-yellow-500;
}

.daqi-level-7-today,
.daqi-alert-after-today-selected-level-7 {
@apply text-white bg-orange-500;
}

.daqi-level-8-today,
.daqi-alert-after-today-selected-level-8 {
@apply text-white bg-red-500;
}

.daqi-level-9-today,
.daqi-alert-after-today-selected-level-9 {
@apply text-white bg-red-800;
}

.daqi-level-10-today,
.daqi-alert-after-today-selected-level-10 {
@apply text-white bg-stone-700;
}
33 changes: 33 additions & 0 deletions app/components/day_tab_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<%= tag.div class: "tab py-4 flex-1 #{@day} mx-2 px-1 text-center #{classes}",
data: {
date: @forecast.date.to_s,
controller: "tab",
"tab-active-class": "active",
"tab-inactive-class": "inactive",
"tab-target": "dayPrediction"
} do
%>
<%= link_to update_styled_forecast_path(day: @day, format: :turbo_stream),
data: {
action: "click->tab#switch_tab"
} do %>
<div class="day" data-tab-target="day">
<%= @forecast.date == Date.today ? 'Today' : @forecast.date.strftime('%A') %>
</div>
<div class="date">
<%= @forecast.date.strftime("%d %B") %>
</div>
<div class="daqi-symbol flex justify-center items-center daqi-marker text-4xl py-2 h-14">
<div class="size-9 rounded-full <%= daqi_indicator_colour_class %>"></div>
<% if @forecast.air_pollution.value > 3 %>
<%= render partial: "shared/icons/exclamation_triangle", locals: {forecast: @forecast} %>
<% end %>
</div>
<div class="daqi-label text-base">
<%= @forecast.air_pollution.label.capitalize %>
</div>
<div class="daqi-value font-normal" data-tab-target="daqiValue">
Index <%= @forecast.air_pollution.value %>/10
</div>
<% end %>
<% end %>
31 changes: 31 additions & 0 deletions app/components/day_tab_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class DayTabComponent < ViewComponent::Base
def initialize(forecast:, day:)
@forecast = forecast
@day = day
end

TAG_COLOURS = {
1 => "bg-lime-400",
2 => "bg-green-400",
3 => "bg-lime-600",
4 => "bg-yellow-300",
5 => "bg-amber-200",
6 => "bg-yellow-500",
7 => "bg-orange-500",
8 => "bg-red-500",
9 => "bg-red-800",
10 => "bg-stone-700"
}

def daqi_indicator_colour_class
TAG_COLOURS.fetch(@forecast.air_pollution.value)
end

def classes
if @day == :today
"active daqi-level-#{@forecast.air_pollution.value}-today"
else
"inactive after-today"
end
end
end
10 changes: 4 additions & 6 deletions app/controllers/styled_forecasts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ def update
@maptiler_api_key = ENV.fetch("MAPTILER_API_KEY")
forecasts = CercForecastService.latest_forecasts_for(zone).data

day_forecast = forecast_for_day(params.fetch("day"), forecasts)
@day_forecast = forecast_for_day(params.fetch("day"), forecasts)

render turbo_stream: turbo_stream.replace(
"day_predictions",
partial: "predictions",
locals: {forecast: day_forecast}
)
respond_to do |format|
format.turbo_stream
end
end

private
Expand Down
51 changes: 45 additions & 6 deletions app/javascript/controllers/tab_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,58 @@ import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="tab"
export default class extends Controller {
static classes = ["active"];
static targets = ["day"];
static classes = ["active", "inactive"];
static targets = ["dayPrediction", "day", "daqiValue"];

connect() {}

switch_tab() {
this.makeAllTabsInactive();
this.dayTarget.classList.toggle(this.activeClass);
this.removeDaqiAlertClasses();

this.addAlertClassIfNotTodayAndHasAirQualityAlert();
this.dayPredictionTarget.classList.toggle(this.activeClass);
this.dayPredictionTarget.classList.toggle(this.inactiveClass);
}

makeAllTabsInactive() {
document
.querySelectorAll(".tabs .tab")
.forEach((el) => el.classList.remove(this.activeClass));
document.querySelectorAll(".tabs .tab").forEach((el) => {
el.classList.remove(this.activeClass);
el.classList.add(this.inactiveClass);
});
patrickjfl marked this conversation as resolved.
Show resolved Hide resolved
}

addAlertClassIfNotTodayAndHasAirQualityAlert() {
const daqiValue = this.getDaqiValue();
if (this.dayTarget.innerText !== "Today" && daqiValue > 3) {
this.addDaqiAlertClass();
}
}

addDaqiAlertClass() {
const daqiValue = this.getDaqiValue();
this.dayPredictionTarget.classList.add(
`daqi-alert-after-today-selected-level-${daqiValue}`
);
}

removeDaqiAlertClasses() {
document.querySelectorAll(".tabs .tab").forEach((el) => {
const daqiAlertClassRegex = new RegExp(
"daqi-alert-after-today-selected-level-(\\d{1,2})"
);

const fullClassName = Array.from(el.classList).find((className) =>
className.match(daqiAlertClassRegex)
);

if (fullClassName) {
el.classList.remove(fullClassName);
}
});
Comment on lines +40 to +53
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clever! bravo 👏

}

getDaqiValue() {
return this.daqiValueTarget.innerText.split("/")[0].split(" ")[1];
}
}
3 changes: 3 additions & 0 deletions app/views/shared/icons/_exclamation_triangle.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke=<%=forecast.air_pollution.value > 6 ? "white" : "black"%> class="size-8 -ml-[2.125rem]">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
</svg>
12 changes: 12 additions & 0 deletions app/views/styled_forecasts/_alert_guidance.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<%= turbo_frame_tag "alert_guidance" do %>
<% if air_pollution_prediction.air_quality_alert %>
<div class="alert-guidance bg-<%= air_pollution_prediction.air_quality_alert.daqi_label.parameterize %>-alert-guidance-panel m-5 p-10">
<p class="font-semibold mb-4">
<%= t("air_quality_alert.#{air_pollution_prediction.air_quality_alert.daqi_level}.guidance.title") %>
</p>
<div class="text-xs">
<%= t("air_quality_alert.#{air_pollution_prediction.air_quality_alert.daqi_level}.guidance.detail_html") %>
</div>
</div>
<% end %>
<% end %>
31 changes: 0 additions & 31 deletions app/views/styled_forecasts/_day_tab.html.erb

This file was deleted.

7 changes: 4 additions & 3 deletions app/views/styled_forecasts/_forecast_tabs.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
class="tabs flex flex-row items-stretch mt-4 m-1 text-xs font-bold border-b-[3px] border-black"
data-turbo-prefetch="false"
>
<%= render "day_tab", forecast: @forecasts.first, day: :today %>
<%= render "day_tab", forecast: @forecasts.second, day: :tomorrow %>
<%= render "day_tab", forecast: @forecasts.third, day: :day_after_tomorrow %>
<%= render(DayTabComponent.new(forecast: @forecasts.first, day: :today)) %>
<%= render(DayTabComponent.new(forecast: @forecasts.second, day: :tomorrow)) %>
<%= render(DayTabComponent.new(forecast: @forecasts.third, day: :day_after_tomorrow)) %>
</div>
<%= render partial: "alert_guidance", locals: {air_pollution_prediction: @forecasts.first} %>
<%= render partial: "map_selector" %>
<div id="map" class="map w-full bg-green-200 h-80" data-maptiler-api-key="<%= @maptiler_api_key %>">
</div>
Expand Down
2 changes: 2 additions & 0 deletions app/views/styled_forecasts/update.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<%= turbo_stream.replace("day_predictions", partial: "predictions", locals: {forecast: @day_forecast}) %>
<%= turbo_stream.replace("alert_guidance", partial: "alert_guidance", locals: {air_pollution_prediction: @day_forecast}) %>
32 changes: 16 additions & 16 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,28 +54,28 @@ en:
title: Action may be required
detail_html:
Adults and children with lung problems, and adults with heart
problems, who experience symptoms, should <b>think about reducing</b>
problems, who experience symptoms, should think about reducing
strenuous physical activity, particularly outdoors.
high:
guidance:
title: Action required
detail_html:
<b>Think about reducing activity</b>, particularly outdoors, if you
have discomfort such as sore eyes, a cough or sore
throat.<br/><br/>Older people, adults and children with lung problems,
and adults with heart problems should <b>reduce</b> strenuous physical
activity, particularly outdoors, and especially if they experience
symptoms.<br/><br/>People with asthma may need to use their reliever
inhaler more often, but remember to never exceed the stated dose or
take more than your doctor has advised
Think about reducing activity, particularly outdoors, if you have
discomfort such as sore eyes, a cough or sore throat.<br/><br/>Older
people, adults and children with lung problems, and adults with heart
problems should reduce strenuous physical activity, particularly
outdoors, and especially if they experience symptoms.<br/><br/>People
with asthma may need to use their reliever inhaler more often, but
remember to never exceed the stated dose or take more than your doctor
has advised.
very_high:
guidance:
title: Action required
detail_html:
Everyone should <b>reduce</b> strenuous physical activity,
particularly outdoors and especially if you experience symptoms such
as a cough or sore throat.<br/><br/>Older people, adults and children
with lung problems, and adults with heart problems should <b>avoid</b>
strenuous physical activity.<br/><br/>People with asthma may need to
use their reliever inhaler more often, but remember to never exceed
the stated dose or take more than your doctor has advised.
Everyone should reduce strenuous physical activity, particularly
outdoors and especially if you experience symptoms such as a cough or
sore throat.<br/><br/>Older people, adults and children with lung
problems, and adults with heart problems should avoid strenuous
physical activity.<br/><br/>People with asthma may need to use their
reliever inhaler more often, but remember to never exceed the stated
dose or take more than your doctor has advised.
Loading