complete story 4.2 with qa tests
This commit is contained in:
@@ -100,7 +100,7 @@ new class extends Component {
|
||||
|
||||
session()->flash('success', __('messages.timeline_created'));
|
||||
|
||||
$this->redirect(route('admin.dashboard'), navigate: true);
|
||||
$this->redirect(route('admin.timelines.show', $timeline), navigate: true);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
use App\Models\AdminLog;
|
||||
use App\Models\Timeline;
|
||||
use App\Models\TimelineUpdate;
|
||||
use App\Notifications\TimelineUpdateNotification;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
public Timeline $timeline;
|
||||
public string $updateText = '';
|
||||
public ?int $editingUpdateId = null;
|
||||
|
||||
public function mount(Timeline $timeline): void
|
||||
{
|
||||
$this->timeline = $timeline->load(['user', 'updates.admin']);
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'updateText' => ['required', 'string', 'min:10'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'updateText.required' => __('timelines.update_text_required'),
|
||||
'updateText.min' => __('timelines.update_text_min'),
|
||||
];
|
||||
}
|
||||
|
||||
public function addUpdate(): void
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$update = $this->timeline->updates()->create([
|
||||
'admin_id' => auth()->id(),
|
||||
'update_text' => clean($this->updateText),
|
||||
]);
|
||||
|
||||
$this->timeline->user->notify(new TimelineUpdateNotification($update));
|
||||
|
||||
AdminLog::create([
|
||||
'admin_id' => auth()->id(),
|
||||
'action' => 'create',
|
||||
'target_type' => 'timeline_update',
|
||||
'target_id' => $update->id,
|
||||
'new_values' => $update->toArray(),
|
||||
'ip_address' => request()->ip(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$this->updateText = '';
|
||||
$this->timeline->load(['updates.admin']);
|
||||
|
||||
session()->flash('success', __('messages.update_added'));
|
||||
}
|
||||
|
||||
public function editUpdate(int $updateId): void
|
||||
{
|
||||
$update = $this->timeline->updates()->findOrFail($updateId);
|
||||
$this->editingUpdateId = $updateId;
|
||||
$this->updateText = $update->update_text;
|
||||
}
|
||||
|
||||
public function saveEdit(): void
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$update = $this->timeline->updates()->findOrFail($this->editingUpdateId);
|
||||
$oldText = $update->update_text;
|
||||
|
||||
$update->update([
|
||||
'update_text' => clean($this->updateText),
|
||||
]);
|
||||
|
||||
AdminLog::create([
|
||||
'admin_id' => auth()->id(),
|
||||
'action' => 'update',
|
||||
'target_type' => 'timeline_update',
|
||||
'target_id' => $update->id,
|
||||
'old_values' => ['update_text' => $oldText],
|
||||
'new_values' => ['update_text' => clean($this->updateText)],
|
||||
'ip_address' => request()->ip(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$this->editingUpdateId = null;
|
||||
$this->updateText = '';
|
||||
$this->timeline->load(['updates.admin']);
|
||||
|
||||
session()->flash('success', __('messages.update_edited'));
|
||||
}
|
||||
|
||||
public function cancelEdit(): void
|
||||
{
|
||||
$this->editingUpdateId = null;
|
||||
$this->updateText = '';
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-6">
|
||||
<flux:button variant="ghost" :href="route('admin.dashboard')" wire:navigate icon="arrow-left">
|
||||
{{ __('timelines.back_to_timelines') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
{{-- Timeline Header --}}
|
||||
<div class="mb-6 rounded-lg border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ $timeline->case_name }}</flux:heading>
|
||||
@if($timeline->case_reference)
|
||||
<div class="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('timelines.reference') }}: {{ $timeline->case_reference }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<flux:badge :color="$timeline->status->value === 'active' ? 'green' : 'zinc'">
|
||||
{{ $timeline->status->label() }}
|
||||
</flux:badge>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-wrap gap-4 text-sm text-zinc-600 dark:text-zinc-300">
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:icon.user class="size-4" />
|
||||
<span>{{ $timeline->user->full_name }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:icon.envelope class="size-4" />
|
||||
<span>{{ $timeline->user->email }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:icon.calendar class="size-4" />
|
||||
<span>{{ __('timelines.created') }}: {{ $timeline->created_at->format('Y-m-d') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Flash Messages --}}
|
||||
@if(session('success'))
|
||||
<div class="mb-6">
|
||||
<flux:callout variant="success" icon="check-circle">
|
||||
{{ session('success') }}
|
||||
</flux:callout>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Add Update Form --}}
|
||||
<div class="mb-6 rounded-lg border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<flux:heading size="lg" class="mb-4">{{ __('timelines.add_update') }}</flux:heading>
|
||||
|
||||
<form wire:submit="{{ $editingUpdateId ? 'saveEdit' : 'addUpdate' }}">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('timelines.update_text') }} *</flux:label>
|
||||
<flux:textarea
|
||||
wire:model="updateText"
|
||||
rows="4"
|
||||
placeholder="{{ __('timelines.update_placeholder') }}"
|
||||
/>
|
||||
<flux:description>{{ __('timelines.update_min_chars') }}</flux:description>
|
||||
<flux:error name="updateText" />
|
||||
</flux:field>
|
||||
|
||||
<div class="mt-4 flex items-center gap-3">
|
||||
@if($editingUpdateId)
|
||||
<flux:button variant="primary" type="submit">
|
||||
{{ __('timelines.save_edit') }}
|
||||
</flux:button>
|
||||
<flux:button variant="ghost" type="button" wire:click="cancelEdit">
|
||||
{{ __('timelines.cancel') }}
|
||||
</flux:button>
|
||||
@else
|
||||
<flux:button variant="primary" type="submit">
|
||||
{{ __('timelines.add_update_button') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- Timeline Updates --}}
|
||||
<div class="rounded-lg border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<flux:heading size="lg" class="mb-6">{{ __('timelines.updates_history') }}</flux:heading>
|
||||
|
||||
@if($timeline->updates->isEmpty())
|
||||
<div class="text-center py-8 text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('timelines.no_updates') }}
|
||||
</div>
|
||||
@else
|
||||
<div class="relative">
|
||||
{{-- Timeline line --}}
|
||||
<div class="absolute left-4 top-0 bottom-0 w-0.5 bg-zinc-200 dark:bg-zinc-700"></div>
|
||||
|
||||
<div class="space-y-6">
|
||||
@foreach($timeline->updates as $update)
|
||||
<div wire:key="update-{{ $update->id }}" class="relative flex gap-4">
|
||||
{{-- Timeline dot --}}
|
||||
<div class="relative z-10 flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900">
|
||||
<flux:icon.document-text class="size-4 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
|
||||
{{-- Update content --}}
|
||||
<div class="flex-1 rounded-lg border border-zinc-200 bg-zinc-50 p-4 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div class="mb-2 flex flex-wrap items-center justify-between gap-2">
|
||||
<div class="flex flex-wrap items-center gap-3 text-sm">
|
||||
<span class="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{{ $update->admin->full_name }}
|
||||
</span>
|
||||
<span class="text-zinc-500 dark:text-zinc-400">
|
||||
{{ $update->created_at->format('Y-m-d H:i') }}
|
||||
</span>
|
||||
@if($update->updated_at->gt($update->created_at))
|
||||
<flux:badge size="sm" color="amber">
|
||||
{{ __('timelines.edited') }}
|
||||
</flux:badge>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if(!$editingUpdateId)
|
||||
<flux:button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
wire:click="editUpdate({{ $update->id }})"
|
||||
icon="pencil"
|
||||
>
|
||||
{{ __('timelines.edit') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="prose prose-sm dark:prose-invert max-w-none">
|
||||
{!! $update->update_text !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user