CSV export implemented, a few other fixes
This commit is contained in:
parent
3a6e3e8eed
commit
56b72f8038
10 changed files with 245 additions and 21 deletions
|
@ -55,6 +55,7 @@ import "../node_modules/bootstrap-icons/icons/eraser.svg";
|
|||
import "../node_modules/bootstrap-icons/icons/save.svg";
|
||||
import "../node_modules/bootstrap-icons/icons/asterisk.svg";
|
||||
import "../node_modules/bootstrap-icons/icons/card-list.svg";
|
||||
import "../node_modules/bootstrap-icons/icons/file-earmark-spreadsheet.svg";
|
||||
|
||||
// webpack automatically bundles all modules in your
|
||||
// entry points. Those entry points can be configured
|
||||
|
|
|
@ -21,7 +21,7 @@ defmodule Shift73k.Shifts do
|
|||
Repo.all(Shift)
|
||||
end
|
||||
|
||||
defp query_shifts_by_user(user_id) do
|
||||
def query_shifts_by_user(user_id) do
|
||||
from(s in Shift)
|
||||
|> where([s], s.user_id == ^user_id)
|
||||
end
|
||||
|
@ -53,6 +53,25 @@ defmodule Shift73k.Shifts do
|
|||
|> Repo.all()
|
||||
end
|
||||
|
||||
def query_user_shift_dates(user_id) do
|
||||
query_shifts_by_user(user_id)
|
||||
|> select([s], s.date)
|
||||
end
|
||||
|
||||
def get_min_user_shift_date(user_id) do
|
||||
query_user_shift_dates(user_id)
|
||||
|> order_by([s], asc: s.date)
|
||||
|> limit([s], 1)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def get_max_user_shift_date(user_id) do
|
||||
query_user_shift_dates(user_id)
|
||||
|> order_by([s], desc: s.date)
|
||||
|> limit([s], 1)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single shift.
|
||||
|
||||
|
@ -146,4 +165,20 @@ defmodule Shift73k.Shifts do
|
|||
def change_shift(%Shift{} = shift, attrs \\ %{}) do
|
||||
Shift.changeset(shift, attrs)
|
||||
end
|
||||
|
||||
###
|
||||
# UTILS
|
||||
def shift_length(%{time_end: time_end, time_start: time_start}) do
|
||||
time_end
|
||||
|> Time.diff(time_start)
|
||||
|> Integer.floor_div(60)
|
||||
|> shift_length()
|
||||
end
|
||||
|
||||
def shift_length(len_min) when is_integer(len_min) and len_min >= 0, do: len_min
|
||||
def shift_length(len_min) when is_integer(len_min) and len_min < 0, do: 1440 + len_min
|
||||
|
||||
def shift_length(time_end, time_start) do
|
||||
shift_length(%{time_end: time_end, time_start: time_start})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ defmodule Shift73k.Shifts.Templates.ShiftTemplate do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Shift73k.Shifts
|
||||
alias Shift73k.Shifts.Templates.ShiftTemplate
|
||||
|
||||
@app_vars Application.get_env(:shift73k, :app_global_vars, time_zone: "America/New_York")
|
||||
|
@ -45,7 +46,7 @@ defmodule Shift73k.Shifts.Templates.ShiftTemplate do
|
|||
|> validate_length(:subject, count: :codepoints, max: 280)
|
||||
|> validate_length(:location, count: :codepoints, max: 280)
|
||||
|> validate_change(:time_end, fn :time_end, time_end ->
|
||||
shift_length = shift_length(time_end, time_start_from_attrs(attrs))
|
||||
shift_length = Shifts.shift_length(time_end, time_start_from_attrs(attrs))
|
||||
|
||||
cond do
|
||||
shift_length == 0 ->
|
||||
|
@ -67,20 +68,6 @@ defmodule Shift73k.Shifts.Templates.ShiftTemplate do
|
|||
defp time_start_from_attrs(%{time_start: time_start}), do: time_start
|
||||
defp time_start_from_attrs(_), do: nil
|
||||
|
||||
def shift_length(%ShiftTemplate{time_end: time_end, time_start: time_start}) do
|
||||
time_end
|
||||
|> Time.diff(time_start)
|
||||
|> Integer.floor_div(60)
|
||||
|> shift_length()
|
||||
end
|
||||
|
||||
def shift_length(len_min) when is_integer(len_min) and len_min >= 0, do: len_min
|
||||
def shift_length(len_min) when is_integer(len_min) and len_min < 0, do: 1440 + len_min
|
||||
|
||||
def shift_length(time_end, time_start) do
|
||||
shift_length(%ShiftTemplate{time_end: time_end, time_start: time_start})
|
||||
end
|
||||
|
||||
# Get shift attrs from shift template
|
||||
def attrs(%ShiftTemplate{} = shift_template) do
|
||||
shift_template
|
||||
|
|
94
lib/shift73k_web/controllers/user_shifts_csv_controller.ex
Normal file
94
lib/shift73k_web/controllers/user_shifts_csv_controller.ex
Normal file
|
@ -0,0 +1,94 @@
|
|||
defmodule Shift73kWeb.UserShiftsCsvController do
|
||||
use Shift73kWeb, :controller
|
||||
|
||||
alias Shift73k.Shifts
|
||||
alias Shift73k.Shifts.Shift
|
||||
|
||||
def new(conn, _params) do
|
||||
render(conn, "new.html", error_message: nil)
|
||||
end
|
||||
|
||||
def export(conn, %{"csv_export" => request_params}) do
|
||||
IO.inspect(request_params, label: "csv request params :")
|
||||
|
||||
case Map.get(request_params, "user_id") == conn.assigns.current_user.id do
|
||||
true ->
|
||||
export_csv(conn, request_params)
|
||||
|
||||
false ->
|
||||
conn
|
||||
|> put_flash(:danger, "Unauthorized CSV export request")
|
||||
|> redirect(to: "/")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
defp export_csv(conn, %{"date_min" => date_min, "date_max" => date_max}) do
|
||||
date_range = Date.range(Date.from_iso8601!(date_min), Date.from_iso8601!(date_max))
|
||||
csv_content = build_csv_content(conn.assigns.current_user.id, date_range)
|
||||
filename_dt = DateTime.utc_now() |> Calendar.strftime("%Y%m%dT%H%M%S")
|
||||
filename = "#{conn.assigns.current_user.id}_#{filename_dt}.csv"
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("text/csv")
|
||||
|> put_resp_header("content-disposition", "attachment; filename=\"#{filename}\"")
|
||||
|> send_resp(200, csv_content)
|
||||
end
|
||||
|
||||
def build_csv_content(user_id, date_range) do
|
||||
[csv_headers() | csv_data(user_id, date_range)]
|
||||
|> NimbleCSV.RFC4180.dump_to_iodata()
|
||||
|> to_string()
|
||||
|> IO.inspect()
|
||||
end
|
||||
|
||||
def csv_data(user_id, date_range) do
|
||||
user_id
|
||||
|> Shifts.list_shifts_by_user_in_date_range(date_range)
|
||||
|> Enum.map(&csv_shift_row/1)
|
||||
end
|
||||
|
||||
def csv_headers do
|
||||
[
|
||||
"Subject",
|
||||
"Start Date",
|
||||
"Start Time",
|
||||
"End Date",
|
||||
"End Time",
|
||||
"All Day Event",
|
||||
"Description",
|
||||
"Location",
|
||||
"Private"
|
||||
]
|
||||
end
|
||||
|
||||
defp csv_shift_row(%Shift{} = s) do
|
||||
dt_start = DateTime.new!(s.date, s.time_start, s.time_zone)
|
||||
shift_length_s = Shifts.shift_length(s) * 60
|
||||
dt_end = DateTime.add(dt_start, shift_length_s)
|
||||
|
||||
[
|
||||
s.subject,
|
||||
csv_date_string(dt_start),
|
||||
csv_time_string(dt_start),
|
||||
csv_date_string(dt_end),
|
||||
csv_time_string(dt_end),
|
||||
false,
|
||||
s.description,
|
||||
s.location,
|
||||
false
|
||||
]
|
||||
end
|
||||
|
||||
defp csv_date_string(%DateTime{} = dt) do
|
||||
dt
|
||||
|> DateTime.to_date()
|
||||
|> Calendar.strftime("%m/%d/%Y")
|
||||
end
|
||||
|
||||
defp csv_time_string(%DateTime{} = dt) do
|
||||
dt
|
||||
|> DateTime.to_time()
|
||||
|> Calendar.strftime("%-I:%M %p")
|
||||
end
|
||||
end
|
|
@ -5,7 +5,7 @@ defmodule Shift73kWeb.LiveHelpers do
|
|||
alias Shift73k.Accounts
|
||||
alias Shift73k.Accounts.User
|
||||
alias Shift73kWeb.UserAuth
|
||||
alias Shift73k.Shifts.Templates.ShiftTemplate
|
||||
alias Shift73k.Shifts
|
||||
|
||||
@doc """
|
||||
Performs the {:noreply, socket} for a given socket.
|
||||
|
@ -74,9 +74,9 @@ defmodule Shift73kWeb.LiveHelpers do
|
|||
|> String.trim_trailing("m")
|
||||
end
|
||||
|
||||
def format_shift_length(%ShiftTemplate{} = shift_template) do
|
||||
shift_template
|
||||
|> ShiftTemplate.shift_length()
|
||||
def format_shift_length(%{} = shift_or_template) do
|
||||
shift_or_template
|
||||
|> Shifts.shift_length()
|
||||
|> format_shift_length()
|
||||
end
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
My Shifts
|
||||
</h2>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="row justify-content-start justify-content-sm-center">
|
||||
<div class="col-md-10 col-xl-10">
|
||||
|
||||
|
@ -60,7 +63,11 @@
|
|||
<span class="text-muted">(<%= shift.location %>)</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div style="font-size: smaller;"><%= shift.description %></div>
|
||||
<%= if shift.description do %>
|
||||
<div style="font-size: smaller;">
|
||||
<%= text_to_html shift.description %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div style="font-size: smaller;">
|
||||
<span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: shift.id, data: [confirm: "Are you sure?"] %></span>
|
||||
</div>
|
||||
|
|
|
@ -98,6 +98,9 @@ defmodule Shift73kWeb.Router do
|
|||
live "/assign", ShiftAssignLive.Index, :index
|
||||
|
||||
live "/shifts", ShiftLive.Index, :index
|
||||
|
||||
get "/csv", UserShiftsCsvController, :new
|
||||
post "/csv", UserShiftsCsvController, :export
|
||||
end
|
||||
|
||||
# scope "/", Shift73kWeb do
|
||||
|
|
|
@ -26,6 +26,15 @@
|
|||
<% end %>
|
||||
</li>
|
||||
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<%# user_shifts_csv_path %>
|
||||
<li>
|
||||
<%= link nav_link_opts(@conn, to: Routes.user_shifts_csv_path(@conn, :new), class: "dropdown-item") do %>
|
||||
<%= icon_div @conn, "bi-file-earmark-spreadsheet", [class: "icon baseline me-1"] %>
|
||||
CSV Export
|
||||
<% end %>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
|
75
lib/shift73k_web/templates/user_shifts_csv/new.html.eex
Normal file
75
lib/shift73k_web/templates/user_shifts_csv/new.html.eex
Normal file
|
@ -0,0 +1,75 @@
|
|||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-10 col-xl-8">
|
||||
|
||||
<h2>
|
||||
<%= icon_div @conn, "bi-file-earmark-spreadsheet", [class: "icon baseline"] %>
|
||||
CSV Export
|
||||
</h2>
|
||||
<p class="lead">Select a date range for which to export a CSV of your scheduled shifts, or click "Export All" to export everything.</p>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-sm-9 col-md-8 col-lg-6 col-xxl-5">
|
||||
|
||||
|
||||
<% min_date = min_user_shift_date(@current_user.id) %>
|
||||
<% max_date = max_user_shift_date(@current_user.id) %>
|
||||
<% today = Date.utc_today() %>
|
||||
|
||||
<%= form_for :csv_export, Routes.user_shifts_csv_path(@conn, :export), fn csv_range -> %>
|
||||
|
||||
<div class="row gx-2 gx-sm-3 mb-3">
|
||||
|
||||
<%= hidden_input csv_range, :user_id, value: @current_user.id %>
|
||||
|
||||
<div class="col-6">
|
||||
<%= label csv_range, :date_min, "From", class: "form-label" %>
|
||||
<%= date_input csv_range, :date_min,
|
||||
value: Date.beginning_of_month(today),
|
||||
min: min_date,
|
||||
max: max_date,
|
||||
class: "form-control"
|
||||
%>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<%= label csv_range, :date_max, "To", class: "form-label" %>
|
||||
<%= date_input csv_range, :date_max,
|
||||
value: Date.end_of_month(today),
|
||||
min: min_date,
|
||||
max: max_date,
|
||||
class: "form-control"
|
||||
%>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row gx-2 gx-sm-3 mb-3">
|
||||
<div class="col text-end">
|
||||
<%= submit "Export for selected dates", class: "btn btn-primary" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
|
||||
|
||||
<%= form_for :csv_export, Routes.user_shifts_csv_path(@conn, :export), fn csv_all -> %>
|
||||
|
||||
<%= hidden_input csv_all, :user_id, value: @current_user.id %>
|
||||
<%= hidden_input csv_all, :date_min, value: min_date %>
|
||||
<%= hidden_input csv_all, :date_max, value: max_date %>
|
||||
|
||||
|
||||
<div class="row gx-2 gx-sm-3 mb-3">
|
||||
<div class="col text-end">
|
||||
<%= submit "Export all", class: "btn btn-outline-primary" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
13
lib/shift73k_web/views/user_shifts_csv_view.ex
Normal file
13
lib/shift73k_web/views/user_shifts_csv_view.ex
Normal file
|
@ -0,0 +1,13 @@
|
|||
defmodule Shift73kWeb.UserShiftsCsvView do
|
||||
use Shift73kWeb, :view
|
||||
|
||||
alias Shift73k.Shifts
|
||||
|
||||
def min_user_shift_date(user_id) do
|
||||
Shifts.get_min_user_shift_date(user_id) |> Date.to_string()
|
||||
end
|
||||
|
||||
def max_user_shift_date(user_id) do
|
||||
Shifts.get_max_user_shift_date(user_id) |> Date.to_string()
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue