CluPilotCloud/resources/views/livewire/admin/payment-problems.blade.php

169 lines
10 KiB
PHP

<div class="mx-auto max-w-[1120px] space-y-6">
<div class="animate-rise">
<h1 class="text-2xl font-bold tracking-tight text-ink">{{ __('payment_problems.title') }}</h1>
<p class="mt-1 max-w-[70ch] text-sm text-muted">{{ __('payment_problems.subtitle') }}</p>
</div>
{{-- Die Einstellungen zugeklappt: die liest man einmal beim Einrichten,
die Fälle darunter jeden Tag. Aufgeklappt stünde die Arbeit hinter der
Konfiguration. --}}
<div x-data="{ open: false }" class="overflow-hidden rounded-lg border border-line bg-surface shadow-xs animate-rise">
<button type="button" x-on:click="open = ! open" :aria-expanded="open"
class="flex w-full items-center justify-between gap-3 px-6 py-4 text-left transition-colors hover:bg-surface-hover">
<span class="flex items-center gap-2">
<x-ui.icon name="chevron-down" class="size-4 shrink-0 text-muted transition-transform"
x-bind:class="open ? '' : '-rotate-90'" />
<span class="font-semibold text-ink">{{ __('payment_problems.schedule_title') }}</span>
</span>
<span class="shrink-0 text-xs text-muted">
{{ implode(' · ', array_map(fn ($d) => $d.'d', $days)) }}
</span>
</button>
<form wire:submit="saveSchedule" x-show="open" x-cloak class="border-t border-line px-6 py-5">
{{-- Zwei Blöcke mit eigener Überschrift statt zwei Reihen untereinander.
Vorher standen Tage und Geldbeträge in derselben Gruppe: oben fünf
Felder mit Tagen, direkt darunter drei mit einer Stufennummer und
zwei Beträgen. Was welche Einheit hat, stand nur in den einzelnen
Beschriftungen und wer schnell etwas ändert, liest die nicht. --}}
<div class="space-y-6">
<div>
<p class="text-xs font-semibold uppercase tracking-wide text-muted">{{ __('payment_problems.schedule_days_title') }}</p>
<p class="mt-1 max-w-[70ch] text-sm text-muted">{{ __('payment_problems.schedule_body') }}</p>
<div class="mt-3 grid gap-3 sm:grid-cols-5">
@foreach ([0, 1, 2, 3, 4] as $stufe)
<x-ui.input :name="'days.'.$stufe" type="number" min="0" max="365"
wire:model="days.{{ $stufe }}"
:label="__('payment_problems.day_'.$stufe)"
:suffix="__('payment_problems.unit_days')" />
@endforeach
</div>
@error('days')<p class="mt-1.5 text-xs text-danger">{{ $message }}</p>@enderror
</div>
<div class="border-t border-line pt-5">
<p class="text-xs font-semibold uppercase tracking-wide text-muted">{{ __('payment_problems.schedule_fees_title') }}</p>
<p class="mt-1 max-w-[70ch] text-sm text-muted">{{ __('payment_problems.schedule_fees_body') }}</p>
<div class="mt-3 grid gap-3 sm:grid-cols-3">
<x-ui.input name="feeFromLevel" type="number" min="1" max="3"
wire:model="feeFromLevel" :label="__('payment_problems.fee_from')" />
<x-ui.input name="fees.2" inputmode="decimal" wire:model="fees.2"
:label="__('payment_problems.fee_2')" suffix="" />
<x-ui.input name="fees.3" inputmode="decimal" wire:model="fees.3"
:label="__('payment_problems.fee_3')" suffix="" />
</div>
</div>
</div>
<div class="mt-5 border-t border-line pt-5">
<x-ui.button type="submit" variant="primary" wire:loading.attr="disabled" wire:target="saveSchedule">
{{ __('common.save') }}
</x-ui.button>
</div>
</form>
</div>
{{-- 1. Laufende Verträge im Rückstand. --}}
<div class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise">
<div>
<h2 class="font-semibold text-ink">{{ __('payment_problems.cases_title') }}</h2>
<p class="mt-1 text-sm text-muted">{{ __('payment_problems.cases_body') }}</p>
</div>
@if ($cases->isEmpty())
<p class="text-sm text-muted">{{ __('payment_problems.cases_none') }}</p>
@else
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-line text-left text-xs font-medium text-muted">
<th class="py-2 pr-4">{{ __('payment_problems.customer') }}</th>
<th class="py-2 pr-4">{{ __('payment_problems.level') }}</th>
<th class="py-2 pr-4">{{ __('payment_problems.since') }}</th>
<th class="py-2 pr-4">{{ __('payment_problems.next_step') }}</th>
<th class="py-2"></th>
</tr>
</thead>
<tbody>
@foreach ($cases as $case)
<tr wire:key="case-{{ $case->id }}" class="border-b border-line last:border-0">
<td class="py-3 pr-4 text-ink">{{ $case->subscription?->customer?->name ?? '—' }}</td>
<td class="py-3 pr-4">
<span @class([
'rounded-pill border px-2.5 py-0.5 text-xs font-medium',
'border-danger-border bg-danger-bg text-danger' => $case->isSuspended(),
'border-warning-border bg-warning-bg text-warning' => ! $case->isSuspended(),
])>{{ __('payment_problems.level_'.$case->level) }}</span>
</td>
{{-- R19: alles, was ein Mensch liest, geht über ->local(). --}}
<td class="py-3 pr-4 text-muted">{{ $case->opened_at->local()->isoFormat('D. MMM YYYY') }}</td>
<td class="py-3 pr-4 text-muted">
{{ $case->next_step_at?->local()->isoFormat('D. MMM YYYY') ?? '—' }}
</td>
{{-- R20: beide Eingriffe tragen ein Feld, gehen also
im Modal auf. Der Knopf schickt nur openModal. --}}
<td class="py-3 text-right">
<span class="inline-flex flex-wrap justify-end gap-2">
@unless ($case->isSuspended())
<x-ui.button variant="secondary" size="sm"
x-on:click="$dispatch('openModal', { component: 'admin.extend-dunning-deadline', arguments: { id: {{ $case->id }} } })">
{{ __('payment_problems.extend') }}
</x-ui.button>
@endunless
<x-ui.button variant="ghost" size="sm"
x-on:click="$dispatch('openModal', { component: 'admin.close-dunning-case', arguments: { id: {{ $case->id }} } })">
{{ __('payment_problems.close') }}
</x-ui.button>
</span>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
{{-- 2. Käufe, deren Zahlung Tage später geplatzt ist. --}}
<div class="space-y-4 rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise [animation-delay:30ms]">
<div>
<h2 class="font-semibold text-ink">{{ __('payment_problems.failed_title') }}</h2>
<p class="mt-1 max-w-[70ch] text-sm text-muted">{{ __('payment_problems.failed_body') }}</p>
</div>
@if ($failed->isEmpty())
<p class="text-sm text-muted">{{ __('payment_problems.failed_none') }}</p>
@else
<ul class="space-y-3">
@foreach ($failed as $problem)
<li wire:key="failed-{{ $problem->id }}" class="rounded-lg border border-line bg-surface-2 p-4">
<div class="flex flex-wrap items-start justify-between gap-3">
<div class="min-w-0">
<p class="text-sm font-medium text-ink">{{ $problem->name ?: $problem->email }}</p>
<p class="mt-0.5 font-mono text-xs text-muted">{{ $problem->email }}</p>
<p class="mt-1 text-xs text-muted">
{{ $problem->plan ?? '—' }} ·
{{ number_format($problem->amount_cents / 100, 2, ',', '.') }} {{ $problem->currency }} ·
{{ $problem->failed_at->local()->isoFormat('D. MMM YYYY, HH:mm') }}
</p>
</div>
{{-- R20: die Begründung wird im Modal eingetippt, nicht
in der Zeile. Ein Feld hier liesse die Zeile
wachsen und die Nachbarn springen und ein
Knopf, der eine Methode am Seiten-Bauteil ruft,
ist der Inline-Editor unter anderem Namen. --}}
<x-ui.button variant="secondary" class="shrink-0"
x-on:click="$dispatch('openModal', { component: 'admin.resolve-failed-checkout', arguments: { id: {{ $problem->id }} } })">
{{ __('payment_problems.resolve') }}
</x-ui.button>
</div>
</li>
@endforeach
</ul>
@endif
</div>
</div>