Certified/resources/views/pages/certificates/⚡edit.blade.php
Joël van de Wouw ac340d125c
Some checks failed
linter / quality (push) Failing after 13s
tests / ci (8.3) (push) Failing after 2s
tests / ci (8.4) (push) Failing after 2s
tests / ci (8.5) (push) Failing after 3s
Initial commit
Certificate manager built on Laravel 13, Livewire 4, and Flux.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 14:44:58 +02:00

142 lines
5.3 KiB
PHP

<?php
use App\Models\Category;
use App\Models\Certificate;
use Flux\Flux;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Title;
use Livewire\Component;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
use Livewire\WithFileUploads;
new #[Title('Edit certificate')] class extends Component {
use WithFileUploads;
public Certificate $certificate;
public string $title = '';
public string $category_id = '';
public string $issuer = '';
public string $certificate_number = '';
public string $issued_at = '';
public string $expires_at = '';
public string $notes = '';
public ?TemporaryUploadedFile $file = null;
public function mount(Certificate $certificate): void
{
// Route-model binding respects the global scope, so a foreign
// certificate resolves to a 404.
$this->certificate = $certificate;
$this->title = $certificate->title;
$this->category_id = (string) ($certificate->category_id ?? '');
$this->issuer = (string) ($certificate->issuer ?? '');
$this->certificate_number = (string) ($certificate->certificate_number ?? '');
$this->issued_at = $certificate->issued_at?->toDateString() ?? '';
$this->expires_at = $certificate->expires_at->toDateString();
$this->notes = (string) ($certificate->notes ?? '');
}
public function save(): void
{
$validated = $this->validate([
'title' => ['required', 'string', 'max:255'],
'category_id' => ['nullable', 'integer', 'exists:categories,id'],
'issuer' => ['nullable', 'string', 'max:255'],
'certificate_number' => ['nullable', 'string', 'max:255'],
'issued_at' => ['nullable', 'date', 'before_or_equal:expires_at'],
'expires_at' => ['required', 'date'],
'notes' => ['nullable', 'string', 'max:2000'],
'file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:10240'],
]);
if ($this->file !== null) {
Storage::disk('local')->delete($this->certificate->file_path);
$this->certificate->file_path = $this->file->storeAs(
'certificates/'.Auth::id(),
Str::uuid().'.'.$this->file->getClientOriginalExtension(),
'local'
);
}
$this->certificate->fill([
'title' => $validated['title'],
'category_id' => $validated['category_id'] ?: null,
'issuer' => $validated['issuer'] ?: null,
'certificate_number' => $validated['certificate_number'] ?: null,
'issued_at' => $validated['issued_at'] ?: null,
'expires_at' => $validated['expires_at'],
'notes' => $validated['notes'] ?: null,
])->save();
Flux::toast(variant: 'success', text: __('Certificate updated.'));
$this->redirectRoute('certificates.index', navigate: true);
}
/**
* @return Collection<int, Category>
*/
#[Computed]
public function categories(): Collection
{
return Category::orderBy('name')->get();
}
}; ?>
<section class="mx-auto flex w-full max-w-xl flex-col gap-6">
<div>
<flux:heading size="xl">{{ __('Edit certificate') }}</flux:heading>
<flux:text class="mt-1">{{ __('Update the details or replace the stored document.') }}</flux:text>
</div>
<form wire:submit="save" class="space-y-6">
<flux:input wire:model="title" :label="__('Title')" required />
<flux:select wire:model="category_id" :label="__('Category')">
<flux:select.option value="">{{ __('Uncategorized') }}</flux:select.option>
@foreach ($this->categories as $category)
<flux:select.option value="{{ $category->id }}">{{ $category->name }}</flux:select.option>
@endforeach
</flux:select>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<flux:input wire:model="issuer" :label="__('Issuing authority')" :placeholder="__('e.g. VCA, Red Cross')" />
<flux:input wire:model="certificate_number" :label="__('Certificate number')" />
<flux:input type="date" wire:model="issued_at" :label="__('Issue date')" />
<flux:input type="date" wire:model="expires_at" :label="__('Expiry date')" required />
</div>
<flux:textarea wire:model="notes" :label="__('Notes')" rows="3" :placeholder="__('Optional notes about this certificate')" />
<flux:input
type="file"
wire:model="file"
:label="__('Replace document')"
accept=".pdf,.jpg,.jpeg,.png"
:description="__('Optional. Leave empty to keep the current file.')"
/>
<div class="flex items-center gap-3">
<flux:button type="submit" variant="primary">{{ __('Save changes') }}</flux:button>
<flux:button
:href="route('certificates.download', $certificate)"
variant="ghost"
icon="arrow-down-tray"
>{{ __('Download current file') }}</flux:button>
<flux:button :href="route('certificates.index')" wire:navigate variant="ghost">{{ __('Cancel') }}</flux:button>
</div>
</form>
</section>