Packages

phoenix_kit

1.7.77
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules shop web carts.ex
Raw

lib/modules/shop/web/carts.ex

defmodule PhoenixKit.Modules.Shop.Web.Carts do
@moduledoc """
Carts admin list LiveView for E-Commerce module.
"""
use PhoenixKitWeb, :live_view
alias PhoenixKit.Modules.Billing.Currency
alias PhoenixKit.Modules.Shop
alias PhoenixKit.Utils.Routes
@per_page 25
@impl true
def mount(_params, _session, socket) do
{carts, total} = Shop.list_carts_with_count(per_page: @per_page)
currency = Shop.get_default_currency()
socket =
socket
|> assign(:page_title, "Shopping Carts")
|> assign(:carts, carts)
|> assign(:total, total)
|> assign(:page, 1)
|> assign(:per_page, @per_page)
|> assign(:status_filter, nil)
|> assign(:search, "")
|> assign(:currency, currency)
{:ok, socket}
end
@impl true
def handle_params(params, _uri, socket) do
page = String.to_integer(params["page"] || "1")
status = params["status"]
search = params["search"] || ""
{carts, total} =
Shop.list_carts_with_count(
page: page,
per_page: @per_page,
status: status,
search: search
)
socket =
socket
|> assign(:carts, carts)
|> assign(:total, total)
|> assign(:page, page)
|> assign(:status_filter, status)
|> assign(:search, search)
{:noreply, socket}
end
@impl true
def handle_event("filter_status", %{"status" => status}, socket) do
status = if status == "", do: nil, else: status
{:noreply, push_patch(socket, to: build_url(socket.assigns, status: status, page: 1))}
end
@impl true
def handle_event("search", %{"search" => search}, socket) do
{:noreply, push_patch(socket, to: build_url(socket.assigns, search: search, page: 1))}
end
defp build_url(assigns, overrides) do
params =
%{
status: Keyword.get(overrides, :status, assigns.status_filter),
search: Keyword.get(overrides, :search, assigns.search),
page: Keyword.get(overrides, :page, assigns.page)
}
|> Enum.filter(fn {_k, v} -> v && v != "" end)
|> URI.encode_query()
if params == "" do
Routes.path("/admin/shop/carts")
else
Routes.path("/admin/shop/carts?#{params}")
end
end
@impl true
def render(assigns) do
~H"""
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={@phoenix_kit_current_scope}
current_path={@url_path}
current_locale={@current_locale}
page_title={@page_title}
>
<div class="container flex-col mx-auto px-4 py-6 max-w-7xl">
<.admin_page_header back={Routes.path("/admin/shop")}>
<h1 class="text-xl sm:text-2xl lg:text-3xl font-bold text-base-content">Shopping Carts</h1>
<p class="text-sm sm:text-base text-base-content/60 mt-0.5">{@total} carts total</p>
</.admin_page_header>
<%!-- Controls Bar --%>
<div class="bg-base-200 rounded-lg p-6 mb-6">
<div class="flex flex-col lg:flex-row gap-4">
<%!-- Search --%>
<div class="flex-1">
<label class="label"><span class="label-text">Search</span></label>
<form phx-submit="search" phx-change="search">
<input
type="text"
name="search"
value={@search}
placeholder="Search by email or session ID..."
class="input input-bordered w-full focus:input-primary"
phx-debounce="300"
/>
</form>
</div>
<%!-- Status Filter --%>
<div class="w-full lg:w-48">
<label class="label"><span class="label-text">Status</span></label>
<select
class="select select-bordered w-full focus:select-primary"
phx-change="filter_status"
name="status"
>
<option value="" selected={is_nil(@status_filter)}>All Status</option>
<option value="active" selected={@status_filter == "active"}>Active</option>
<option value="converted" selected={@status_filter == "converted"}>Converted</option>
<option value="abandoned" selected={@status_filter == "abandoned"}>Abandoned</option>
<option value="expired" selected={@status_filter == "expired"}>Expired</option>
<option value="merged" selected={@status_filter == "merged"}>Merged</option>
</select>
</div>
</div>
</div>
<%!-- Carts Table --%>
<div class="card bg-base-100 shadow-xl overflow-hidden">
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th>Customer</th>
<th>Items</th>
<th class="text-right">Total</th>
<th>Status</th>
<th>Updated</th>
</tr>
</thead>
<tbody>
<%= if @carts == [] do %>
<tr>
<td colspan="5" class="text-center py-12 text-base-content/50">
<.icon name="hero-shopping-cart" class="w-12 h-12 mx-auto mb-3 opacity-50" />
<p class="text-lg">No carts found</p>
<p class="text-sm">Carts will appear here when customers add items</p>
</td>
</tr>
<% else %>
<%= for cart <- @carts do %>
<tr class="hover">
<td>
<%= if cart.user do %>
<div class="font-medium">{cart.user.email}</div>
<div class="text-xs text-base-content/50">User UUID: {cart.user.uuid}</div>
<% else %>
<div class="text-base-content/60">Guest</div>
<div class="text-xs text-base-content/40 font-mono">
{String.slice(cart.session_id || "", 0, 16)}...
</div>
<% end %>
</td>
<td>
<span class="badge badge-neutral">{cart.items_count || 0} items</span>
<%= if cart.total_weight_grams && cart.total_weight_grams > 0 do %>
<span class="text-xs text-base-content/50 ml-2">
{format_weight(cart.total_weight_grams)}
</span>
<% end %>
</td>
<td class="text-right">
<div class="font-semibold">{format_price(cart.total, @currency)}</div>
<%= if Decimal.compare(cart.subtotal || Decimal.new("0"), cart.total || Decimal.new("0")) != :eq do %>
<div class="text-xs text-base-content/50">
Subtotal: {format_price(cart.subtotal, @currency)}
</div>
<% end %>
</td>
<td>
<span class={status_badge_class(cart.status)}>{cart.status}</span>
</td>
<td>
<div class="text-sm">{format_datetime(cart.updated_at)}</div>
<%= if cart.expires_at do %>
<div class="text-xs text-base-content/50">
Expires: {format_datetime(cart.expires_at)}
</div>
<% end %>
</td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
</div>
<%!-- Pagination --%>
<%= if @total > @per_page do %>
<div class="flex justify-center mt-6">
<div class="join">
<%= for page_num <- 1..ceil(@total / @per_page) do %>
<.link
patch={build_url(assigns, page: page_num)}
class={["join-item btn btn-sm", if(@page == page_num, do: "btn-active")]}
>
{page_num}
</.link>
<% end %>
</div>
</div>
<% end %>
</div>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>
"""
end
defp status_badge_class("active"), do: "badge badge-success"
defp status_badge_class("converted"), do: "badge badge-info"
defp status_badge_class("abandoned"), do: "badge badge-warning"
defp status_badge_class("expired"), do: "badge badge-neutral"
defp status_badge_class("merged"), do: "badge badge-secondary"
defp status_badge_class(_), do: "badge"
defp format_price(nil, _currency), do: "-"
defp format_price(amount, %Currency{} = currency) do
Currency.format_amount(amount, currency)
end
defp format_price(amount, nil) do
"$#{Decimal.round(amount, 2)}"
end
defp format_weight(grams) when grams >= 1000, do: "#{Float.round(grams / 1000, 1)} kg"
defp format_weight(grams), do: "#{grams} g"
defp format_datetime(nil), do: "-"
defp format_datetime(datetime) do
Calendar.strftime(datetime, "%Y-%m-%d %H:%M")
end
end