CluPilotCloud/resources/views/livewire/admin/finance.blade.php

145 lines
9.4 KiB
PHP

<div class="space-y-5">
<div class="animate-rise">
<h1 class="text-2xl font-bold tracking-tight text-ink">{{ __('finance.title') }}</h1>
<p class="mt-1 text-sm text-muted">{{ __('finance.subtitle') }}</p>
</div>
{{-- What is still missing before anything may be issued. An invoice without
a name, an address or a VAT number is not a valid invoice, and issuing
one is worse than refusing to. --}}
@if ($missing !== [])
<x-ui.alert variant="warning" class="animate-rise">
{{ __('finance.incomplete', ['fields' => collect($missing)->map(fn ($f) => __('finance.field.'.$f))->join(', ')]) }}
</x-ui.alert>
@endif
<form wire:submit="saveCompany" class="space-y-5 rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise [animation-delay:60ms]">
<div>
<h2 class="font-semibold text-ink">{{ __('finance.company_title') }}</h2>
<p class="mt-1 max-w-2xl text-sm text-muted">{{ __('finance.company_sub') }}</p>
</div>
<div class="grid gap-4 sm:grid-cols-2">
<x-ui.input name="company.name" wire:model="company.name" :label="__('finance.field.name')" class="sm:col-span-2" />
<x-ui.input name="company.address" wire:model="company.address" :label="__('finance.field.address')" class="sm:col-span-2" />
<x-ui.input name="company.postcode" wire:model="company.postcode" :label="__('finance.field.postcode')" />
<x-ui.input name="company.city" wire:model="company.city" :label="__('finance.field.city')" />
<x-ui.input name="company.country" wire:model="company.country" :label="__('finance.field.country')" />
<x-ui.input name="company.phone" wire:model="company.phone" :label="__('finance.field.phone')" />
<x-ui.input name="company.email" wire:model="company.email" :label="__('finance.field.email')" />
<x-ui.input name="company.website" wire:model="company.website" :label="__('finance.field.website')" />
</div>
<div class="grid gap-4 border-t border-line pt-5 sm:grid-cols-2">
<x-ui.input name="company.vat_id" wire:model="company.vat_id" :label="__('finance.field.vat_id')" :hint="__('finance.hint.vat_id')" placeholder="ATU12345678" />
<x-ui.input name="company.register_number" wire:model="company.register_number" :label="__('finance.field.register_number')" placeholder="FN 123456a" />
<x-ui.input name="company.register_court" wire:model="company.register_court" :label="__('finance.field.register_court')" placeholder="Handelsgericht Wien" />
<div>
<label class="text-sm font-medium text-body" for="tax-rate">{{ __('finance.field.tax_rate') }}</label>
<input id="tax-rate" type="number" step="0.1" min="0" max="100" wire:model="taxRate"
class="mt-1.5 w-full rounded-md border border-line-strong bg-surface px-3 py-2 font-mono text-sm text-ink" />
<p class="mt-1 text-xs text-muted">{{ __('finance.hint.tax_rate') }}</p>
@error('taxRate')<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
</div>
</div>
<div class="grid gap-4 border-t border-line pt-5 sm:grid-cols-3">
<x-ui.input name="company.bank_name" wire:model="company.bank_name" :label="__('finance.field.bank_name')" />
<x-ui.input name="company.iban" wire:model="company.iban" :label="__('finance.field.iban')" />
<x-ui.input name="company.bic" wire:model="company.bic" :label="__('finance.field.bic')" />
</div>
<div class="grid gap-4 border-t border-line pt-5 sm:grid-cols-3">
<x-ui.input name="company.payment_days" wire:model="company.payment_days" type="number" :label="__('finance.field.payment_days')" />
<x-ui.input name="company.payment_terms" wire:model="company.payment_terms" :label="__('finance.field.payment_terms')" class="sm:col-span-2" />
</div>
{{-- The logo. Replaceable, because a company changes its mark and every
invoice rendered after that has to carry the new one while every
invoice already issued keeps the one it was issued with, because
the path is copied into the document at issue. --}}
<div class="border-t border-line pt-5">
<label class="text-sm font-medium text-body">{{ __('finance.field.logo') }}</label>
<div class="mt-2 flex flex-wrap items-center gap-4">
<div class="grid size-20 place-items-center overflow-hidden rounded-lg border border-line bg-surface-2">
@if ($logo)
<img src="{{ $logo->temporaryUrl() }}" alt="" class="max-h-16 max-w-16 object-contain" />
@elseif ($logoPath)
<img src="{{ Storage::disk('public')->url($logoPath) }}" alt="" class="max-h-16 max-w-16 object-contain" />
@else
<x-ui.icon name="receipt" class="size-6 text-muted" />
@endif
</div>
<div class="min-w-0">
<input type="file" wire:model="logo" accept="image/png,image/webp"
class="block w-full text-sm text-body file:mr-3 file:rounded-md file:border file:border-line-strong file:bg-surface-2 file:px-3 file:py-1.5 file:text-sm file:text-body" />
<p class="mt-1 text-xs text-muted">{{ __('finance.hint.logo') }}</p>
@error('logo')<p class="mt-1 text-xs text-danger">{{ $message }}</p>@enderror
@if ($logoPath)
<button type="button" wire:click="removeLogo" class="mt-2 text-xs font-medium text-danger underline">{{ __('finance.logo_remove') }}</button>
@endif
</div>
</div>
</div>
{{-- The archive. A directory, nothing more: the mount behind it is the
operator's business, and keeping it that way means the target can
change without a line of code changing with it. --}}
<div class="border-t border-line pt-5">
<x-ui.input name="archivePath" wire:model="archivePath" :label="__('finance.field.archive_path')"
:hint="__('finance.hint.archive_path')" placeholder="/mnt/rechnungen" />
@if ($archivePath !== '')
<p class="mt-2 text-xs {{ $exportFailures > 0 ? 'text-danger' : 'text-muted' }}">
@if ($exportFailures > 0)
{{ __('finance.archive_failed', ['n' => $exportFailures]) }}
@elseif ($unexported > 0)
{{ __('finance.archive_pending', ['n' => $unexported]) }}
@else
{{ __('finance.archive_clear') }}
@endif
</p>
@endif
</div>
<div class="flex justify-end border-t border-line pt-5">
<x-ui.button variant="primary" type="submit" wire:loading.attr="disabled">{{ __('finance.save') }}</x-ui.button>
</div>
</form>
{{-- The Rechnungskreise. Each has its own prefix and its own gapless
counter — a credit note is not an invoice with a minus sign. --}}
<div class="overflow-hidden rounded-lg border border-line bg-surface shadow-xs animate-rise [animation-delay:90ms]">
<div class="border-b border-line p-6">
<h2 class="font-semibold text-ink">{{ __('finance.series_title') }}</h2>
<p class="mt-1 max-w-2xl text-sm text-muted">{{ __('finance.series_sub') }}</p>
</div>
<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">{{ __('finance.series.name') }}</th>
<th class="px-4 py-3 font-semibold">{{ __('finance.series.prefix') }}</th>
<th class="px-4 py-3 font-semibold">{{ __('finance.series.next') }}</th>
<th class="px-4 py-3 font-semibold">{{ __('finance.series.reset') }}</th>
<th class="px-4 py-3 text-right font-semibold">{{ __('finance.series.actions') }}</th>
</tr>
</thead>
<tbody>
@foreach ($series as $row)
<tr wire:key="series-{{ $row->uuid }}" class="border-b border-line last:border-0">
<td class="px-4 py-3 text-body">{{ $row->name }}</td>
<td class="px-4 py-3 font-mono font-semibold text-ink">{{ $row->prefix }}</td>
<td class="px-4 py-3 font-mono text-xs text-muted">{{ str_pad((string) $row->next_number, $row->digits, '0', STR_PAD_LEFT) }}</td>
<td class="px-4 py-3 text-xs text-muted">{{ $row->yearly_reset ? __('finance.series.reset_yearly') : __('finance.series.reset_never') }}</td>
<td class="px-4 py-3 text-right">
<x-ui.button variant="secondary"
x-on:click="$dispatch('openModal', { component: 'admin.edit-invoice-series', arguments: { uuid: '{{ $row->uuid }}' } })">
{{ __('finance.series.edit') }}
</x-ui.button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>