Packages

phoenix_kit

1.7.25
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="p-6 max-w-7xl mx-auto">
<div class="flex justify-between items-center mb-6">
<div>
<h1 class="text-3xl font-bold">Shopping Carts</h1>
<p class="text-base-content/70">{@total} carts total</p>
</div>
</div>
<%!-- Filters --%>
<div class="card bg-base-100 shadow mb-6">
<div class="card-body py-4">
<div class="flex flex-col md:flex-row gap-4">
<div class="form-control flex-1">
<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"
phx-debounce="300"
/>
</form>
</div>
<select
class="select select-bordered w-full md:w-48"
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-lg 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 ID: {cart.user.id}</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