towerops/lib/towerops_web/live/admin/org_live/index.html.heex

306 lines
12 KiB
Text

<Layouts.admin flash={@flash} timezone={@timezone}>
<div class="space-y-6">
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{t("All Organizations")}</h1>
<p class="text-gray-600 dark:text-gray-400">{t_admin("Manage organizations")}</p>
</div>
<!-- Global Billing Defaults Card -->
<div
class="rounded-lg border border-slate-200 bg-white p-6 dark:border-slate-700 dark:bg-slate-800"
data-test="global-defaults-card"
>
<div class="flex items-center justify-between">
<div>
<h3 class="text-lg font-medium text-slate-900 dark:text-white">
Global Billing Defaults
</h3>
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">
Default pricing for all organizations (can be overridden per-org)
</p>
</div>
<button
type="button"
phx-click="edit_global_defaults"
data-test="edit-global-defaults"
class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500"
>
Edit Defaults
</button>
</div>
<dl class="mt-6 grid grid-cols-2 gap-6">
<div>
<dt class="text-sm font-medium text-slate-500 dark:text-slate-400">Free Devices</dt>
<dd
class="mt-1 text-2xl font-semibold text-slate-900 dark:text-white"
data-test="default-free-devices"
>
{@default_free_devices}
</dd>
</div>
<div>
<dt class="text-sm font-medium text-slate-500 dark:text-slate-400">
Price Per Device
</dt>
<dd
class="mt-1 text-2xl font-semibold text-slate-900 dark:text-white"
data-test="default-price"
>
${@default_price}/mo
</dd>
</div>
</dl>
</div>
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
<.table id="organizations" rows={@organizations}>
<:col :let={org} label={t("Name")}>{org.name}</:col>
<:col :let={org} label={t("Slug")}>{org.slug}</:col>
<:col :let={org} label={t("Plan")}>
<span class={[
"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium",
if(org.subscription_plan == "paid",
do: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400",
else: "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"
)
]}>
{org.subscription_plan}
</span>
</:col>
<:col :let={org} label={t("Devices")}>
{org.device_count}
</:col>
<:col :let={org} label={t("Overrides")}>
<%= if org.custom_free_device_limit || org.custom_price_per_device do %>
<div class="flex flex-wrap gap-1">
<%= if org.custom_free_device_limit do %>
<span class="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400">
{org.custom_free_device_limit} {t("free")}
</span>
<% end %>
<%= if org.custom_price_per_device do %>
<span class="inline-flex items-center rounded-full bg-purple-100 px-2 py-0.5 text-xs font-medium text-purple-700 dark:bg-purple-900/30 dark:text-purple-400">
${org.custom_price_per_device}/{t("device")}
</span>
<% end %>
</div>
<% else %>
<span class="text-gray-400 dark:text-gray-500 text-xs">{t("Default")}</span>
<% end %>
</:col>
<:col :let={org} label={t("Members")}>{length(org.memberships)}</:col>
<:col :let={org} label={t("Created")}>
{ToweropsWeb.TimeHelpers.format_date(org.inserted_at, @timezone)}
</:col>
<:col :let={org} label="">
<div class="flex items-center gap-3">
<.button
id={"edit-overrides-#{org.id}"}
phx-click="edit_overrides"
phx-value-id={org.id}
variant="secondary"
>
{t_admin("Edit")}
</.button>
<.button
phx-click="delete_org"
phx-value-id={org.id}
data-confirm={
t(
"Are you sure? This will delete all sites, device, and data for this organization."
)
}
variant="danger"
>
{t("Delete")}
</.button>
</div>
</:col>
</.table>
</div>
<%!-- Billing Override Edit Panel --%>
<%= if @editing_org do %>
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10 p-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
{t_admin("Billing Overrides")} — {@editing_org.name}
</h2>
<button
id="cancel-overrides-btn"
phx-click="cancel_overrides"
class="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
>
{t("Cancel")}
</button>
</div>
<.form
for={@override_form}
id="billing-override-form"
phx-change="validate_overrides"
phx-submit="save_overrides"
>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<.input
field={@override_form[:custom_free_device_limit]}
type="number"
label={t_admin("Custom Free Device Limit")}
placeholder="10 (default)"
min="1"
max="9999"
/>
<.input
field={@override_form[:custom_price_per_device]}
type="number"
label={t_admin("Custom Price Per Device")}
placeholder="1.00 (default)"
step="0.01"
min="0"
max="999.99"
/>
</div>
<div class="mt-4 flex items-center gap-3">
<.button type="submit" phx-disable-with={t("Saving...")}>
{t("Save")}
</.button>
<button
id="clear-overrides-btn"
type="button"
phx-click="clear_overrides"
class="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
>
{t_admin("Reset to Defaults")}
</button>
</div>
</.form>
</div>
<% end %>
<%!-- Edit Global Defaults Modal --%>
<%= if @editing_global do %>
<div
class="fixed inset-0 z-50 overflow-y-auto"
data-test="global-defaults-modal"
>
<div class="flex min-h-screen items-center justify-center p-4">
<div
class="fixed inset-0 bg-slate-500 bg-opacity-75"
phx-click="cancel_global_edit"
>
</div>
<div class="relative w-full max-w-lg rounded-lg bg-white p-6 shadow-xl dark:bg-slate-800">
<h3 class="text-lg font-medium text-slate-900 dark:text-white">
Edit Global Billing Defaults
</h3>
<.form
id="global-pricing-form"
for={@global_form}
phx-change="validate_global_pricing"
phx-submit="save_global_pricing"
data-test="global-defaults-form"
class="mt-6 space-y-4"
>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300">
Free Devices
</label>
<input
type="number"
name="global_pricing[default_free_devices]"
value={@global_form.data["default_free_devices"]}
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-slate-600 dark:bg-slate-700"
/>
<%= if error = @global_form.errors[:default_free_devices] do %>
<p
id="global_pricing_default_free_devices-error"
class="mt-1 text-sm text-red-600"
>
{elem(error, 0)}
</p>
<% end %>
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300">
Price Per Device (USD/month)
</label>
<input
type="number"
step="0.01"
name="global_pricing[default_price_per_device]"
value={@global_form.data["default_price_per_device"]}
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:border-slate-600 dark:bg-slate-700"
/>
<%= if error = @global_form.errors[:default_price_per_device] do %>
<p
id="global_pricing_default_price_per_device-error"
class="mt-1 text-sm text-red-600"
>
{elem(error, 0)}
</p>
<% end %>
</div>
<div class="flex justify-end gap-3 pt-4">
<button
type="button"
phx-click="cancel_global_edit"
data-test="cancel-global-edit"
class="rounded-md px-3 py-2 text-sm font-semibold text-slate-900 hover:bg-slate-100 dark:text-white dark:hover:bg-slate-700"
>
Cancel
</button>
<button
type="submit"
class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500"
>
Save Changes
</button>
</div>
</.form>
<%!-- Confirmation Dialog --%>
<%= if @show_confirm do %>
<div
class="absolute inset-0 flex items-center justify-center bg-slate-900/50"
data-test="confirm-pricing-change"
>
<div class="w-full max-w-sm rounded-lg bg-white p-6 shadow-xl dark:bg-slate-800">
<h4 class="text-lg font-medium text-slate-900 dark:text-white">
Confirm Price Change
</h4>
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">
This will update {@active_sub_count} active subscriptions to the new price.
Changes take effect at the next billing cycle.
</p>
<div class="mt-6 flex justify-end gap-3">
<button
type="button"
phx-click="cancel_global_edit"
class="rounded-md px-3 py-2 text-sm font-semibold text-slate-900 hover:bg-slate-100 dark:text-white dark:hover:bg-slate-700"
>
Cancel
</button>
<button
type="button"
phx-click="confirm_pricing_update"
data-test="confirm-pricing-update"
class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500"
>
Confirm Update
</button>
</div>
</div>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</Layouts.admin>