120 lines
9.4 KiB
PHP
120 lines
9.4 KiB
PHP
<div class="space-y-5">
|
|
<div class="animate-rise">
|
|
<h1 class="text-2xl font-bold tracking-tight text-ink">{{ __('admin.nav.customers') }}</h1>
|
|
<p class="mt-1 text-sm text-muted">{{ __('admin.customers_sub') }}</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-5 lg:grid-cols-[1fr_300px] lg:items-start">
|
|
<div class="overflow-hidden rounded-lg border border-line bg-surface shadow-xs animate-rise [animation-delay:60ms]">
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm">
|
|
<thead>
|
|
<tr class="border-b border-line bg-surface-2 text-left text-xs font-semibold text-muted">
|
|
<th class="px-4 py-3 font-semibold">{{ __('admin.col.customer') }}</th>
|
|
<th class="px-4 py-3 font-semibold">{{ __('admin.col.plan') }}</th>
|
|
<th class="px-4 py-3 font-semibold">{{ __('admin.col.mrr') }}</th>
|
|
<th class="px-4 py-3 font-semibold">{{ __('admin.col.status') }}</th>
|
|
<th class="px-4 py-3 text-right font-semibold">{{ __('admin.col.actions') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse ($rows as $r)
|
|
<tr class="border-b border-line last:border-0 hover:bg-surface-hover">
|
|
<td class="px-4 py-3">
|
|
{{-- The row leads somewhere now. The list
|
|
was the end of the road: an operator
|
|
answering a question had the name here
|
|
and the mail client in another window. --}}
|
|
<a href="{{ route('admin.customer', $r['uuid']) }}" wire:navigate
|
|
class="font-medium text-ink hover:underline">{{ $r['name'] }}</a>
|
|
<p class="font-mono text-xs text-muted">{{ $r['instance'] }}.clupilot.com</p>
|
|
</td>
|
|
<td class="px-4 py-3 text-body">
|
|
{{ $r['plan'] }}
|
|
@if ($r['granted'])
|
|
<span class="ml-1.5 inline-flex items-center gap-1 rounded-pill bg-accent-bg px-1.5 py-0.5 text-[11px] font-medium text-accent-text">
|
|
<x-ui.icon name="tag" class="size-3" />
|
|
{{ __('admin.granted_badge') }}
|
|
</span>
|
|
@endif
|
|
@if ($r['pending_change'])
|
|
{{-- Booked, not yet landed. Said on the
|
|
row that answers "what is this
|
|
customer on", because that is where
|
|
the question is asked. --}}
|
|
<p class="mt-0.5 flex items-center gap-1 text-xs text-muted">
|
|
<x-ui.icon name="calendar" class="size-4 text-warning" />
|
|
{{ __('admin.pending_change', ['plan' => $r['pending_change']['plan'], 'date' => $r['pending_change']['at']]) }}
|
|
</p>
|
|
@endif
|
|
</td>
|
|
<td class="px-4 py-3 font-mono text-body">{{ $r['mrr'] }}</td>
|
|
<td class="px-4 py-3"><x-ui.badge :status="$r['status']">{{ __('admin.status.'.$r['status']) }}</x-ui.badge></td>
|
|
<td class="px-4 py-3">
|
|
<div class="flex items-center justify-end gap-1.5">
|
|
@unless ($r['closed'])
|
|
<button type="button" wire:click="toggleSuspend('{{ $r['uuid'] }}')"
|
|
class="inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-xs font-semibold
|
|
{{ $r['suspended'] ? 'border-success-border text-success hover:bg-success-bg' : 'border-line text-muted hover:border-warning hover:text-warning' }}">
|
|
{{ $r['suspended'] ? __('admin.reactivate') : __('admin.suspend') }}
|
|
</button>
|
|
@endunless
|
|
@if (auth()->user()?->can('customers.grant_plan'))
|
|
<button type="button" title="{{ __('admin.grant_action') }}"
|
|
x-on:click="$dispatch('openModal', { component: 'admin.grant-plan', arguments: { uuid: '{{ $r['uuid'] }}' } })"
|
|
class="inline-flex items-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-xs font-semibold text-body hover:border-accent-border hover:text-accent-text">
|
|
<x-ui.icon name="tag" class="size-3.5" />
|
|
{{ __('admin.grant_action') }}
|
|
</button>
|
|
@endif
|
|
{{-- A withdrawal that came in by telephone or by post. Only
|
|
where the window is genuinely open — an operator offered
|
|
a button that would be refused is an operator who
|
|
promises a customer something we then take back. --}}
|
|
@if ($r['withdrawal_open'] && auth()->user()?->can('customers.manage'))
|
|
<button type="button" title="{{ __('withdrawal.admin_action') }}"
|
|
x-on:click="$dispatch('openModal', { component: 'admin.record-withdrawal', arguments: { uuid: '{{ $r['uuid'] }}' } })"
|
|
class="inline-flex items-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-xs font-semibold text-body hover:border-warning hover:text-warning">
|
|
<x-ui.icon name="rotate-ccw" class="size-3.5" />
|
|
{{ __('withdrawal.admin_action') }}
|
|
</button>
|
|
@endif
|
|
{{-- Impersonation borrows the customer's PORTAL session; this
|
|
is administrator access to their Nextcloud itself. Two
|
|
different things, so two buttons. --}}
|
|
@if ($r['instance_uuid'] && auth()->user()?->can('instances.adminlogin'))
|
|
<button type="button" title="{{ __('instances.admin_action') }}"
|
|
x-on:click="$dispatch('openModal', { component: 'admin.instance-admin-access', arguments: { uuid: '{{ $r['instance_uuid'] }}' } })"
|
|
class="inline-flex items-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-xs font-semibold text-body hover:border-accent-border hover:text-accent-text">
|
|
<x-ui.icon name="shield" class="size-3.5" />
|
|
{{ __('instances.admin_action') }}
|
|
</button>
|
|
@endif
|
|
<form method="POST" action="{{ route('admin.impersonate', $r['uuid']) }}" class="inline">
|
|
@csrf
|
|
<button type="submit"
|
|
class="inline-flex items-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-xs font-semibold text-body hover:border-accent-border hover:text-accent-text">
|
|
<x-ui.icon name="log-out" class="size-3.5" />
|
|
{{ __('admin.impersonate') }}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="5" class="px-4 py-10 text-center text-sm text-muted">{{ __('admin.customers_empty') }}</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="rounded-lg border border-line bg-surface p-5 shadow-xs animate-rise [animation-delay:120ms]">
|
|
<h2 class="font-semibold text-ink">{{ __('admin.by_plan') }}</h2>
|
|
<div class="mt-3 h-56" wire:ignore><x-ui.chart :config="$plansChart" class="h-56" :label="__('admin.by_plan')" /></div>
|
|
</div>
|
|
</div>
|
|
</div>
|