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: 12 additions & 1 deletion app/javascript/packs/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,12 @@ document.addEventListener("DOMContentLoaded", () => {


const countHours = () => {
const expectedHours = parseInt(document.querySelectorAll(".expected-hours").innerText)
const expectedHoursElement = document.querySelectorAll(".expected-hours")[0]
let expectedHours = 0
if (expectedHoursElement) {
expectedHours = parseInt(expectedHoursElement.innerText)
}

const currentHours = document.getElementById("member-hours")
const elements = document.querySelectorAll("#forecast-form-container .hour-input")
let total = 0
Expand All @@ -82,6 +87,12 @@ document.addEventListener("DOMContentLoaded", () => {
if (currentHours) {
currentHours.innerText = total
}

if (currentHours && total >= expectedHours) {
currentHours.classList.add("text-green-600", "font-bold")
} else if (currentHours) {
currentHours.classList.remove("text-green-600", "font-bold")
}
}

countHours()
Expand Down
3 changes: 2 additions & 1 deletion app/mailers/reminder_mailer.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
class ReminderMailer < ApplicationMailer
default template_path: 'reminders/reminder_mailer'

def reminder(member, monthly_forecast)
def reminder(member, monthly_forecast, message)
@team = member.teams&.first
@member = member
@monthly_forecast = monthly_forecast
@message = message
mail(
to: member.email,
subject: "[ACTION REQUIRED] Submit #{monthly_forecast.friendly_name} forecast"
Expand Down
7 changes: 4 additions & 3 deletions app/services/send_reminder.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
class SendReminder < ApplicationService

def initialize(teams, monthly_forecast, members=nil)
def initialize(teams, monthly_forecast, message: nil, members: nil)
@teams = teams
@monthly_forecast = monthly_forecast
@message = message
end

def call
Expand All @@ -11,12 +12,12 @@ def call

private

attr_reader :teams, :monthly_forecast
attr_reader :teams, :monthly_forecast, :message

def execute
forecasts = MemberForecast.get_member_forecasts(teams, monthly_forecast).select { |member| member.hours.nil? || member.hours == 0 }
Member.includes(:teams).where(id: forecasts.map(&:id)).each do |member|
ReminderMailer.reminder(member, monthly_forecast).deliver_now
ReminderMailer.reminder(member, monthly_forecast, message).deliver_now
end
end
end
56 changes: 35 additions & 21 deletions app/views/devise/sessions/new.html.erb
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
<h2>Log in</h2>

<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="flex justify-center">
<div class="min-w-64 w-full sm:w-7/12 md:w-1/2 lg:w-4/12 bg-white px-4 sm:px-8 py-8 my-16 shadow-sm rounded">
<h2 class="text-lg font-bold mb-4">Log in</h2>
<div class="my-4">
<% if notice.present? %>
<p class="notice"><%= notice %></p>
<% end %>
<% if alert.present? %>
<p class="alert bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"><%= alert %></p>
<% end %>
</div>
<div class="my-4">
<p class="text-gray-500">Only managers and select members can login to manage team data</p>
</div>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<div class="field mb-2">
<%= f.label :email, class: "text-gray-700"%><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email", class: "mt-1 block w-full rounded-md border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" %>
</div>

<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password, autocomplete: "current-password" %>
</div>
<div class="field mb-2">
<%= f.label :password, class: "text-gray-700" %><br />
<%= f.password_field :password, autocomplete: "current-password", class: "mt-1 block w-full rounded-md border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" %>
</div>

<% if devise_mapping.rememberable? %>
<div class="field">
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
</div>
<% end %>
<% if devise_mapping.rememberable? %>
<div class="field mb-2 inline-flex items-center">
<%= f.check_box :remember_me, class: "rounded border-gray-300 text-indigo-600 focus:border-indigo-300 focus:ring focus:ring-offset-0 focus:ring-indigo-200 focus:ring-opacity-50" %>
<%= f.label :remember_me, class: "pl-2 text-gray-700" %>
</div>
<% end %>

<div class="actions">
<%= f.submit "Log in" %>
<div class="actions mt-2">
<%= f.submit "Log in", class: "w-full cursor-pointer bg-indigo-600 text-white text-base font-semibold py-2 px-4 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-indigo-200" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
</div>
<% end %>

<%= render "devise/shared/links" %>
</div>
14 changes: 8 additions & 6 deletions app/views/devise/shared/_links.html.erb
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
<div class="mt-8">
<%- if controller_name != 'sessions' %>
<%= link_to "Log in", new_session_path(resource_name) %><br />
<%= link_to "Log in", new_session_path(resource_name), class: "no-underline hover:underline text-indigo-500" %><br />
<% end %>

<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to "Sign up", new_registration_path(resource_name) %><br />
<%= link_to "Sign up", new_registration_path(resource_name), class: "no-underline hover:underline text-indigo-500"%><br />
<% end %>

<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
<%= link_to "Forgot your password?", new_password_path(resource_name), class: "no-underline hover:underline text-indigo-500" %><br />
<% end %>

<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), class: "no-underline hover:underline text-indigo-500"%><br />
<% end %>

<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name), class: "no-underline hover:underline text-indigo-500" %><br />
<% end %>

<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %><br />
<%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post, class: "no-underline hover:underline text-indigo-500" %><br />
<% end %>
<% end %>
</div>
36 changes: 19 additions & 17 deletions app/views/forecasts/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
<h2><%= @team.name.titleize %> Forecasts</h2>
<% if @monthly_forecasts.size > 0 %>
<ul>
<% @monthly_forecasts.each do |forecast| %>
<li>
<%=
link_to forecast.friendly_name, forecast_path(
team_name: @team.slug,
year: forecast.date.year,
month: forecast.month_name
)
%>
</li>
<div class="mt-8 mb-4 px-4 sm:px-0">
<h2 class="font-bold mb-2"><%= @team.name.titleize %> Forecasts</h2>
<% if @monthly_forecasts.size > 0 %>
<ul>
<% @monthly_forecasts.each do |forecast| %>
<li class="no-underline hover:underline">
<%=
link_to forecast.friendly_name, forecast_path(
team_name: @team.slug,
year: forecast.date.year,
month: forecast.month_name
)
%>
</li>
<% end %>
</ul>
<% else %>
<p>No forecasts</p>
<% end %>
</ul>
<% else %>
<p>No forecasts</p>
<% end %>
</div>
61 changes: 40 additions & 21 deletions app/views/forecasts/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,37 +1,54 @@
<%= link_to "Back", :back %>
<%= link_to "All forecasts", forecasts_path(team_name: @team.slug) %><h2><%= @team.name %></h2>
<h2><%= @team.name.titleize %> <%= @monthly_forecast.friendly_name%></h2>
<%= button_to "Send reminder", send_reminder_path, remote: true, method: :get %>
<table>
<div class="mt-8 mb-4 px-4 sm:px-0">
<div class="mb-4">
<%= link_to "#{@team.name.titleize} team page", team_path(team_name: @team.slug), class: "cursor-pointer no-underline hover:underline mr-4" %>
<%= link_to "All forecasts", forecasts_path(team_name: @team.slug), class: "cursor-pointer no-underline hover:underline mr-4" %>
</div>
<% status_color = @monthly_forecast.active ? "green" : "red" %>
<h2 class="text-lg"><span class="font-bold"><%= @team.name.titleize %></span> <span class="text-gray-500"><%= @monthly_forecast.friendly_name%></span>
<span class="font-bold leading-none text-xs ml-2 align-middle py-1 px-2 text-<%= status_color %>-50 bg-<%= status_color %>-500 rounded"><%= @monthly_forecast.active ? "Open" : "Closed" %></span>
</h2>
<div class="text-gray-700">There are <span class="expected-hours"><b><%= @monthly_forecast&.total_hours %></b></span> hours this month.</div>
</div>
<div class="bg-white mx-0 pt-8 mt-8 mb-16 shadow-sm rounded overflow-x-auto">
<div class="flex justify-between mt-0 m-4">
<div>
<%= button_to "Send Reminder", send_reminder_path, remote: true, method: :get, class: "ml-4 cursor-pointer bg-indigo-600 text-white py-2 px-4 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-indigo-200" %>
</div>
<div>
<%= button_to "Export to Spreadshet", send_reminder_path, remote: true, method: :get, class: "ml-4 cursor-pointer bg-transparent hover:bg-gray-500 text-gray-700 hover:text-white py-2 px-4 border border-gray-500 hover:border-transparent rounded" %>
</div>
</div>
<table class="relative w-full table-auto">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Total</th>
<tr class="text-left uppercase bg-gray-200 text-gray-600 text-sm">
<th class="px-4 py-4">First Name</th>
<th class="px-4 py-4">Last Name</th>
<th class="px-4 py-4">Email</th>
<th class="px-4 py-4">Total</th>
<% if @monthly_forecast.has_holidays? %>
<th>Holiday</th>
<th class="px-4 py-4">Holiday</th>
<% end %>
<% @team.ordered_fields.each do |field| %>
<% if field.name.downcase != "holiday" %>
<th><%= field.name %></th>
<th class="px-4 py-4"><%= field.name %></th>
<% end %>
<% end %>
</tr>
</thead>
<tbody>
<% @member_forecasts.each do |member_forecast| %>
<tr>
<td><%= member_forecast.first_name %></td>
<td><%= member_forecast.last_name %></td>
<td><%= member_forecast.email %></td>
<td><%= member_forecast.total_hours %></td>
<% @member_forecasts.each_with_index do |member_forecast, index| %>
<% odd_even_style = index % 2 == 0 ? "" : " bg-gray-100" %>
<tr class="text-left<%= odd_even_style %>">
<td class="px-4 py-2 sm:py-4"><%= member_forecast.first_name %></td>
<td class="px-4 py-2 sm:py-4"><%= member_forecast.last_name %></td>
<td class="px-4 py-2 sm:py-4"><%= member_forecast.email %></td>
<td class="px-4 py-2 sm:py-4"><%= member_forecast.total_hours %></td>
<% if @monthly_forecast.has_holidays? %>
<td><%= member_forecast.hours&.fetch(:holiday, 0) %></td>
<td class="px-4 py-2 sm:py-4"><%= member_forecast.hours&.fetch(:holiday, 0) %></td>
<% end %>
<% @team.ordered_fields.each do |field| %>
<% if field.name != "holiday" %>
<td>
<td class="px-4 py-2 sm:py-4">
<% if member_forecast.hours&.fetch(field.name).present? %>
<%= member_forecast.hours.fetch(field.name) %>
<% else %>
Expand All @@ -43,4 +60,6 @@
</tr>
<% end %>
</tbody>
</table>
</table>
</div>
</div>
42 changes: 30 additions & 12 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,37 @@
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>

<body>
<div style="padding: 10px 0">
<h2>Forecasts</h2>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<% if member_signed_in? %>
<%= link_to "home", home_path %>
<%= link_to "logout", destroy_member_session_path, method: :delete %>
<% else %>
<%= link_to "login", new_member_session_path %>
<% end %>
<body class="bg-gray-50">
<nav class="flex bg-white py-2 sm:py-4 px-2 sm:px-6 lg:px-8 w-full shadow-sm">
<div class="flex-1">
<h2 class="font-bold text-xl">
<%= link_to "Forecasts", root_path %>
</h2>
</div>
<div class="flex-1 flex justify-end">
<% if member_signed_in? %>
<div class="flex space-x-4">
<%= link_to "Home", home_path, class: "hover:text-gray-500" %>
<%= link_to "Logout", destroy_member_session_path, method: :delete, class: "hover:text-gray-500" %>
</div>
<% else %>
<%= link_to "Login", new_member_session_path, class: "hover:text-gray-500" %>
<% end %>
</div>
</nav>
<% if !devise_controller? %>
<div>
<% if notice.present? %>
<div class="notice"><%= notice %></div>
<% end %>
<% if alert.present? %>
<div class="alert m-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"><%= alert %></div>
<% end %>
</div>
<% end %>
<div class="px-0 sm:px-4 md:px-6 lg:px-8">
<%= yield %>
</div>
<%= yield %>
<%= render "shared/modal" %>
</body>
</html>
12 changes: 7 additions & 5 deletions app/views/reminders/_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<h1>Send Reminder</h1>
<h2 class="font-bold mb-1">Send Reminder</h2>
<p class="text-gray-700 text-sm mb-4">Send reminder to team members who haven't submitted a forecast</p>
<%= form_with id: 'reminder-form', method: :post, local: false do |form| %>
<%= form.text_area :message, size: "70x5" %>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<%= form.label :message, "(Optional) Add Message", class: "text-gray-700 text-sm" %>
<%= form.text_area :message, class:"block w-full mt-1 rounded-md border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" %>
<div class="bg-gray-50 py-3 sm:flex sm:flex-row-reverse">
<button id="modal-submit" type="submit" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-600 text-base font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm">
Send
</button>
<button id="close-modal" type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
<button id="close-modal" type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Cancel
</button>
<% end %>
<% end %>
1 change: 1 addition & 0 deletions app/views/reminders/reminder_mailer/reminder.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<p>Dear <%= @member.first_name %>,</p>
<p>Please submit your forecasts for <%= @monthly_forecast.friendly_name %></p>
<p>There are <%= @monthly_forecast.total_hours %> hours this month</p>
<p><%= @message%> </p>
<p>You can submit your forecast <%=
link_to "here", forecast_url(
team_name: @team.slug,
Expand Down
2 changes: 1 addition & 1 deletion app/views/shared/_modal.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
From: "opacity-100 translate-y-0 sm:scale-100"
To: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
-->
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden p-4 shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div id="modal-content"></div>
</div>
</div>
Expand Down
Loading