302 lines
12 KiB
Elixir
302 lines
12 KiB
Elixir
defmodule ActionRequestsDemoWeb.ActionRequestsLive do
|
|
use ActionRequestsDemoWeb, :live_view
|
|
alias ActionRequestsDemo.ActionRequests
|
|
|
|
def mount(_params, _session, socket) do
|
|
{:ok, assign(socket, current_user_id: 1)}
|
|
end
|
|
|
|
def handle_params(params, _uri, socket) do
|
|
case ActionRequests.list_action_requests(params, socket.assigns.current_user_id) do
|
|
{:ok, {action_requests, meta}} ->
|
|
{:noreply,
|
|
socket
|
|
|> assign(:action_requests, action_requests)
|
|
|> assign(:meta, meta)
|
|
|> assign(:params, params)}
|
|
|
|
{:error, _meta} ->
|
|
{:noreply,
|
|
socket
|
|
|> assign(:action_requests, [])
|
|
|> assign(:meta, %Flop.Meta{})
|
|
|> assign(:params, params)}
|
|
end
|
|
end
|
|
|
|
def handle_event("filter", params, socket) do
|
|
# Navigate with new params, which will trigger handle_params
|
|
{:noreply, push_patch(socket, to: ~p"/?#{params}")}
|
|
end
|
|
|
|
def handle_event("patch", params, socket) do
|
|
# Handle pagination events from paginator links
|
|
{:noreply, push_patch(socket, to: ~p"/?#{params}")}
|
|
end
|
|
|
|
def render(assigns) do
|
|
~H"""
|
|
<div class="px-4 py-8 sm:px-6 lg:px-8">
|
|
<div class="mx-auto max-w-7xl">
|
|
<h1 class="text-3xl font-bold mb-8">Action Requests</h1>
|
|
|
|
<.form
|
|
for={%{}}
|
|
method="get"
|
|
action={~p"/"}
|
|
phx-submit="filter"
|
|
phx-change="filter"
|
|
class="mb-6"
|
|
>
|
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-white mb-1">Status</label>
|
|
<select
|
|
name="status"
|
|
class="w-full rounded-md border border-gray-400 shadow-sm focus:border-blue-500 focus:ring-blue-500 px-3 py-2"
|
|
>
|
|
<option value="" selected={@params["status"] in [nil, "", "all"]}>
|
|
All
|
|
</option>
|
|
<option value="resolved" selected={@params["status"] == "resolved"}>
|
|
Resolved
|
|
</option>
|
|
<option value="unresolved" selected={@params["status"] == "unresolved"}>
|
|
Unresolved
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-white mb-1">Assignment</label>
|
|
<select
|
|
name="assignment"
|
|
class="w-full rounded-md border border-gray-400 shadow-sm focus:border-blue-500 focus:ring-blue-500 px-3 py-2"
|
|
>
|
|
<option value="" selected={@params["assignment"] in [nil, ""]}>
|
|
All
|
|
</option>
|
|
<option value="mine" selected={@params["assignment"] == "mine"}>
|
|
Mine
|
|
</option>
|
|
<option value="assigned" selected={@params["assignment"] == "assigned"}>
|
|
Assigned
|
|
</option>
|
|
<option value="unassigned" selected={@params["assignment"] == "unassigned"}>
|
|
Unassigned
|
|
</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-white mb-1">Patient Name</label>
|
|
<input
|
|
type="text"
|
|
name="patient_name"
|
|
value={@params["patient_name"] || ""}
|
|
placeholder="Search by name..."
|
|
autocomplete="off"
|
|
class="w-full rounded-md border border-gray-400 shadow-sm focus:border-blue-500 focus:ring-blue-500 px-3 py-2 leading-4"
|
|
phx-debounce="100"
|
|
/>
|
|
</div>
|
|
|
|
<div class="flex items-end">
|
|
<noscript>
|
|
<button
|
|
type="submit"
|
|
class="w-full rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500"
|
|
>
|
|
Apply Filters
|
|
</button>
|
|
</noscript>
|
|
</div>
|
|
</div>
|
|
</.form>
|
|
|
|
<div class="overflow-x-auto rounded-lg border border-gray-200">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
|
<.sort_link meta={@meta} params={@params} field={:patient_name}>Patient Name</.sort_link>
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
|
<.sort_link meta={@meta} params={@params} field={:status}>Status</.sort_link>
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
|
Assigned To
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
|
<.sort_link meta={@meta} params={@params} field={:inserted_at}>Created</.sort_link>
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
|
<.sort_link meta={@meta} params={@params} field={:delivery_scheduled_at}>
|
|
Delivery Scheduled
|
|
</.sort_link>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 bg-white">
|
|
<%= for request <- @action_requests do %>
|
|
<tr>
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-900">
|
|
{request.patient_name}
|
|
</td>
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm">
|
|
<.status_badge status={request.status} />
|
|
</td>
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
|
{if request.assigned_user_id, do: "User ##{request.assigned_user_id}", else: "-"}
|
|
</td>
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
|
{Calendar.strftime(request.inserted_at, "%Y-%m-%d %H:%M")}
|
|
</td>
|
|
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|
|
<%= if request.delivery_scheduled_at do %>
|
|
{Calendar.strftime(request.delivery_scheduled_at, "%Y-%m-%d %H:%M")}
|
|
<% else %>
|
|
-
|
|
<% end %>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="mt-4 flex items-center justify-between">
|
|
<div class="text-sm text-white">
|
|
Page {@meta.current_page} of {@meta.total_pages} (showing {length(@action_requests)} of {@meta.total_count} records)
|
|
</div>
|
|
<nav aria-label="Pagination" class="flex flex-wrap gap-1 items-center justify-center">
|
|
<.link
|
|
patch={~p"/?#{Map.merge(@params, %{"page" => 1})}"}
|
|
class={[
|
|
"rounded-md px-2 py-1 text-sm font-semibold",
|
|
(@meta.current_page == 1 && "bg-blue-600 text-white") ||
|
|
"bg-white text-gray-900 hover:bg-gray-50 shadow-sm ring-1 ring-inset ring-gray-300"
|
|
]}
|
|
aria-current={@meta.current_page == 1 && "page"}
|
|
>
|
|
1
|
|
</.link>
|
|
<%= if @meta.current_page > 4 do %>
|
|
<span class="px-2 py-1 text-gray-500">...</span>
|
|
<% end %>
|
|
<%= for page <- page_window(@meta.current_page, @meta.total_pages, 2) do %>
|
|
<%= if page != 1 and page != @meta.total_pages do %>
|
|
<.link
|
|
patch={~p"/?#{Map.merge(@params, %{"page" => page})}"}
|
|
class={[
|
|
"rounded-md px-2 py-1 text-sm font-semibold",
|
|
(page == @meta.current_page && "bg-blue-600 text-white") ||
|
|
"bg-white text-gray-900 hover:bg-blue-50 shadow-sm ring-1 ring-inset ring-gray-300"
|
|
]}
|
|
aria-current={page == @meta.current_page && "page"}
|
|
>
|
|
{page}
|
|
</.link>
|
|
<% end %>
|
|
<% end %>
|
|
<%= if @meta.current_page < @meta.total_pages - 3 do %>
|
|
<span class="px-2 py-1 text-gray-500">...</span>
|
|
<% end %>
|
|
<%= if @meta.total_pages > 1 do %>
|
|
<.link
|
|
patch={~p"/?#{Map.merge(@params, %{"page" => @meta.total_pages})}"}
|
|
class={[
|
|
"rounded-md px-2 py-1 text-sm font-semibold",
|
|
(@meta.current_page == @meta.total_pages && "bg-blue-600 text-white") ||
|
|
"bg-white text-gray-900 hover:bg-gray-50 shadow-sm ring-1 ring-inset ring-gray-300"
|
|
]}
|
|
aria-current={@meta.current_page == @meta.total_pages && "page"}
|
|
>
|
|
{@meta.total_pages}
|
|
</.link>
|
|
<% end %>
|
|
<.link
|
|
patch={~p"/?#{Map.merge(@params, %{"page" => @meta.previous_page})}"}
|
|
class={[
|
|
"rounded-md px-2 py-1 text-sm font-semibold",
|
|
(!@meta.has_previous_page? && "bg-gray-200 text-gray-500 cursor-not-allowed") ||
|
|
"bg-white text-gray-900 hover:bg-gray-50 shadow-sm ring-1 ring-inset ring-gray-300"
|
|
]}
|
|
aria-disabled={!@meta.has_previous_page?}
|
|
>
|
|
Previous
|
|
</.link>
|
|
<.link
|
|
patch={~p"/?#{Map.merge(@params, %{"page" => @meta.next_page})}"}
|
|
class={[
|
|
"rounded-md px-2 py-1 text-sm font-semibold",
|
|
(!@meta.has_next_page? && "bg-gray-200 text-gray-500 cursor-not-allowed") ||
|
|
"bg-white text-gray-900 hover:bg-gray-50 shadow-sm ring-1 ring-inset ring-gray-300"
|
|
]}
|
|
aria-disabled={!@meta.has_next_page?}
|
|
>
|
|
Next
|
|
</.link>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
end
|
|
|
|
defp page_window(current, total, radius) do
|
|
# Returns a list of page numbers centered around current page, excluding first/last
|
|
start_page = max(current - radius, 2)
|
|
end_page = min(current + radius, total - 1)
|
|
|
|
if end_page >= start_page do
|
|
Enum.to_list(start_page..end_page)
|
|
else
|
|
[]
|
|
end
|
|
end
|
|
|
|
defp status_badge(assigns) do
|
|
~H"""
|
|
<span class={[
|
|
"inline-flex rounded-full px-2 py-1 text-xs font-semibold leading-5",
|
|
if(@status == "resolved",
|
|
do: "bg-green-100 text-green-800",
|
|
else: "bg-yellow-100 text-yellow-800"
|
|
)
|
|
]}>
|
|
{@status}
|
|
</span>
|
|
"""
|
|
end
|
|
|
|
defp sort_link(assigns) do
|
|
current_order =
|
|
if assigns.meta.flop.order_by == [assigns.field],
|
|
do: List.first(assigns.meta.flop.order_directions),
|
|
else: nil
|
|
|
|
next_direction = if current_order == :asc, do: :desc, else: :asc
|
|
|
|
assigns = assign(assigns, :next_direction, next_direction)
|
|
assigns = assign(assigns, :current_order, current_order)
|
|
|
|
~H"""
|
|
<.link
|
|
patch={
|
|
~p"/?#{Map.merge(@params, %{"order_by" => @field, "order_directions" => @next_direction})}"
|
|
}
|
|
class="group inline-flex"
|
|
>
|
|
{render_slot(@inner_block)}
|
|
<%= if @current_order == :asc do %>
|
|
<span class="ml-2">↑</span>
|
|
<% end %>
|
|
<%= if @current_order == :desc do %>
|
|
<span class="ml-2">↓</span>
|
|
<% end %>
|
|
</.link>
|
|
"""
|
|
end
|
|
end
|