complete story 3.5 with qa test
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ConsultationStatus;
|
||||
use App\Enums\ConsultationType;
|
||||
use App\Enums\PaymentStatus;
|
||||
use App\Models\AdminLog;
|
||||
use App\Models\Consultation;
|
||||
use App\Notifications\BookingApproved;
|
||||
use App\Notifications\BookingRejected;
|
||||
use App\Services\CalendarService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Livewire\Volt\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public string $dateFrom = '';
|
||||
public string $dateTo = '';
|
||||
|
||||
public function updatedDateFrom(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function updatedDateTo(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function clearFilters(): void
|
||||
{
|
||||
$this->dateFrom = '';
|
||||
$this->dateTo = '';
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function quickApprove(int $id): void
|
||||
{
|
||||
$consultation = Consultation::with('user')->findOrFail($id);
|
||||
|
||||
if ($consultation->status !== ConsultationStatus::Pending) {
|
||||
session()->flash('error', __('admin.booking_already_processed'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$oldStatus = $consultation->status->value;
|
||||
|
||||
$consultation->update([
|
||||
'status' => ConsultationStatus::Approved,
|
||||
'consultation_type' => ConsultationType::Free,
|
||||
'payment_status' => PaymentStatus::NotApplicable,
|
||||
]);
|
||||
|
||||
// Generate calendar file and send notification
|
||||
try {
|
||||
$calendarService = app(CalendarService::class);
|
||||
$icsContent = $calendarService->generateIcs($consultation);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to generate calendar file', [
|
||||
'consultation_id' => $consultation->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
$icsContent = null;
|
||||
}
|
||||
|
||||
if ($consultation->user) {
|
||||
$consultation->user->notify(
|
||||
new BookingApproved($consultation, $icsContent ?? '', null)
|
||||
);
|
||||
}
|
||||
|
||||
// Log action
|
||||
AdminLog::create([
|
||||
'admin_id' => auth()->id(),
|
||||
'action' => 'approve',
|
||||
'target_type' => 'consultation',
|
||||
'target_id' => $consultation->id,
|
||||
'old_values' => ['status' => $oldStatus],
|
||||
'new_values' => [
|
||||
'status' => ConsultationStatus::Approved->value,
|
||||
'consultation_type' => ConsultationType::Free->value,
|
||||
],
|
||||
'ip_address' => request()->ip(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
session()->flash('success', __('admin.booking_approved'));
|
||||
}
|
||||
|
||||
public function quickReject(int $id): void
|
||||
{
|
||||
$consultation = Consultation::with('user')->findOrFail($id);
|
||||
|
||||
if ($consultation->status !== ConsultationStatus::Pending) {
|
||||
session()->flash('error', __('admin.booking_already_processed'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$oldStatus = $consultation->status->value;
|
||||
|
||||
$consultation->update([
|
||||
'status' => ConsultationStatus::Rejected,
|
||||
]);
|
||||
|
||||
// Send rejection notification
|
||||
if ($consultation->user) {
|
||||
$consultation->user->notify(
|
||||
new BookingRejected($consultation, null)
|
||||
);
|
||||
}
|
||||
|
||||
// Log action
|
||||
AdminLog::create([
|
||||
'admin_id' => auth()->id(),
|
||||
'action' => 'reject',
|
||||
'target_type' => 'consultation',
|
||||
'target_id' => $consultation->id,
|
||||
'old_values' => ['status' => $oldStatus],
|
||||
'new_values' => [
|
||||
'status' => ConsultationStatus::Rejected->value,
|
||||
],
|
||||
'ip_address' => request()->ip(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
session()->flash('success', __('admin.booking_rejected'));
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'bookings' => Consultation::query()
|
||||
->where('status', ConsultationStatus::Pending)
|
||||
->when($this->dateFrom, fn ($q) => $q->where('booking_date', '>=', $this->dateFrom))
|
||||
->when($this->dateTo, fn ($q) => $q->where('booking_date', '<=', $this->dateTo))
|
||||
->with('user:id,full_name,email,phone,user_type')
|
||||
->orderBy('booking_date')
|
||||
->orderBy('booking_time')
|
||||
->paginate(15),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
|
||||
<flux:heading size="xl">{{ __('admin.pending_bookings') }}</flux:heading>
|
||||
</div>
|
||||
|
||||
@if(session('success'))
|
||||
<flux:callout variant="success" class="mb-6">
|
||||
{{ session('success') }}
|
||||
</flux:callout>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<flux:callout variant="danger" class="mb-6">
|
||||
{{ session('error') }}
|
||||
</flux:callout>
|
||||
@endif
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="bg-white dark:bg-zinc-800 rounded-lg p-4 border border-zinc-200 dark:border-zinc-700 mb-6">
|
||||
<div class="flex flex-col sm:flex-row gap-4 items-end">
|
||||
<flux:field class="flex-1">
|
||||
<flux:label>{{ __('admin.date_from') }}</flux:label>
|
||||
<flux:input type="date" wire:model.live="dateFrom" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field class="flex-1">
|
||||
<flux:label>{{ __('admin.date_to') }}</flux:label>
|
||||
<flux:input type="date" wire:model.live="dateTo" />
|
||||
</flux:field>
|
||||
|
||||
@if($dateFrom || $dateTo)
|
||||
<flux:button wire:click="clearFilters" variant="ghost">
|
||||
{{ __('common.clear') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bookings List -->
|
||||
<div class="space-y-4">
|
||||
@forelse($bookings as $booking)
|
||||
<div wire:key="booking-{{ $booking->id }}" class="bg-white dark:bg-zinc-800 rounded-lg p-4 border border-zinc-200 dark:border-zinc-700">
|
||||
<div class="flex flex-col lg:flex-row lg:items-start justify-between gap-4">
|
||||
<!-- Booking Info -->
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
{{ $booking->user?->full_name ?? __('common.unknown') }}
|
||||
</span>
|
||||
<flux:badge variant="warning" size="sm">
|
||||
{{ $booking->status->label() }}
|
||||
</flux:badge>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:icon name="calendar" class="w-4 h-4" />
|
||||
{{ \Carbon\Carbon::parse($booking->booking_date)->translatedFormat('l, d M Y') }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:icon name="clock" class="w-4 h-4" />
|
||||
{{ \Carbon\Carbon::parse($booking->booking_time)->format('g:i A') }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:icon name="envelope" class="w-4 h-4" />
|
||||
{{ $booking->user?->email ?? '-' }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:icon name="document-text" class="w-4 h-4" />
|
||||
{{ __('admin.submitted') }}: {{ $booking->created_at->translatedFormat('d M Y') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-3 text-sm text-zinc-600 dark:text-zinc-400 line-clamp-2">
|
||||
{{ Str::limit($booking->problem_summary, 150) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex flex-wrap gap-2 lg:flex-col">
|
||||
<flux:button
|
||||
href="{{ route('admin.bookings.review', $booking) }}"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('admin.review') }}
|
||||
</flux:button>
|
||||
|
||||
<flux:button
|
||||
wire:click="quickApprove({{ $booking->id }})"
|
||||
wire:confirm="{{ __('admin.confirm_quick_approve') }}"
|
||||
variant="filled"
|
||||
size="sm"
|
||||
>
|
||||
{{ __('admin.quick_approve') }}
|
||||
</flux:button>
|
||||
|
||||
<flux:button
|
||||
wire:click="quickReject({{ $booking->id }})"
|
||||
wire:confirm="{{ __('admin.confirm_quick_reject') }}"
|
||||
variant="danger"
|
||||
size="sm"
|
||||
>
|
||||
{{ __('admin.quick_reject') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="text-center py-12 text-zinc-500 dark:text-zinc-400 bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
||||
<flux:icon name="inbox" class="w-12 h-12 mx-auto mb-4" />
|
||||
<p>{{ __('admin.no_pending_bookings') }}</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
{{ $bookings->links() }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,395 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ConsultationStatus;
|
||||
use App\Enums\ConsultationType;
|
||||
use App\Enums\PaymentStatus;
|
||||
use App\Models\AdminLog;
|
||||
use App\Models\Consultation;
|
||||
use App\Notifications\BookingApproved;
|
||||
use App\Notifications\BookingRejected;
|
||||
use App\Services\CalendarService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component
|
||||
{
|
||||
public Consultation $consultation;
|
||||
|
||||
public string $consultationType = 'free';
|
||||
public ?string $paymentAmount = null;
|
||||
public string $paymentInstructions = '';
|
||||
public string $rejectionReason = '';
|
||||
|
||||
public bool $showApproveModal = false;
|
||||
public bool $showRejectModal = false;
|
||||
|
||||
public function mount(Consultation $consultation): void
|
||||
{
|
||||
$this->consultation = $consultation->load(['user']);
|
||||
}
|
||||
|
||||
public function openApproveModal(): void
|
||||
{
|
||||
$this->showApproveModal = true;
|
||||
}
|
||||
|
||||
public function openRejectModal(): void
|
||||
{
|
||||
$this->showRejectModal = true;
|
||||
}
|
||||
|
||||
public function approve(): void
|
||||
{
|
||||
if ($this->consultation->status !== ConsultationStatus::Pending) {
|
||||
session()->flash('error', __('admin.booking_already_processed'));
|
||||
$this->showApproveModal = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->validate([
|
||||
'consultationType' => ['required', 'in:free,paid'],
|
||||
'paymentAmount' => ['required_if:consultationType,paid', 'nullable', 'numeric', 'min:0'],
|
||||
'paymentInstructions' => ['nullable', 'string', 'max:1000'],
|
||||
]);
|
||||
|
||||
$oldStatus = $this->consultation->status->value;
|
||||
$type = $this->consultationType === 'paid' ? ConsultationType::Paid : ConsultationType::Free;
|
||||
|
||||
$this->consultation->update([
|
||||
'status' => ConsultationStatus::Approved,
|
||||
'consultation_type' => $type,
|
||||
'payment_amount' => $type === ConsultationType::Paid ? $this->paymentAmount : null,
|
||||
'payment_status' => $type === ConsultationType::Paid ? PaymentStatus::Pending : PaymentStatus::NotApplicable,
|
||||
]);
|
||||
|
||||
// Generate calendar file
|
||||
$icsContent = null;
|
||||
try {
|
||||
$calendarService = app(CalendarService::class);
|
||||
$icsContent = $calendarService->generateIcs($this->consultation);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to generate calendar file', [
|
||||
'consultation_id' => $this->consultation->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Send notification with .ics attachment
|
||||
if ($this->consultation->user) {
|
||||
$this->consultation->user->notify(
|
||||
new BookingApproved(
|
||||
$this->consultation,
|
||||
$icsContent ?? '',
|
||||
$this->paymentInstructions ?: null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Log action
|
||||
AdminLog::create([
|
||||
'admin_id' => auth()->id(),
|
||||
'action' => 'approve',
|
||||
'target_type' => 'consultation',
|
||||
'target_id' => $this->consultation->id,
|
||||
'old_values' => ['status' => $oldStatus],
|
||||
'new_values' => [
|
||||
'status' => ConsultationStatus::Approved->value,
|
||||
'consultation_type' => $type->value,
|
||||
'payment_amount' => $this->paymentAmount,
|
||||
],
|
||||
'ip_address' => request()->ip(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
session()->flash('success', __('admin.booking_approved'));
|
||||
$this->redirect(route('admin.bookings.pending'), navigate: true);
|
||||
}
|
||||
|
||||
public function reject(): void
|
||||
{
|
||||
if ($this->consultation->status !== ConsultationStatus::Pending) {
|
||||
session()->flash('error', __('admin.booking_already_processed'));
|
||||
$this->showRejectModal = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->validate([
|
||||
'rejectionReason' => ['nullable', 'string', 'max:1000'],
|
||||
]);
|
||||
|
||||
$oldStatus = $this->consultation->status->value;
|
||||
|
||||
$this->consultation->update([
|
||||
'status' => ConsultationStatus::Rejected,
|
||||
]);
|
||||
|
||||
// Send rejection notification
|
||||
if ($this->consultation->user) {
|
||||
$this->consultation->user->notify(
|
||||
new BookingRejected($this->consultation, $this->rejectionReason ?: null)
|
||||
);
|
||||
}
|
||||
|
||||
// Log action
|
||||
AdminLog::create([
|
||||
'admin_id' => auth()->id(),
|
||||
'action' => 'reject',
|
||||
'target_type' => 'consultation',
|
||||
'target_id' => $this->consultation->id,
|
||||
'old_values' => ['status' => $oldStatus],
|
||||
'new_values' => [
|
||||
'status' => ConsultationStatus::Rejected->value,
|
||||
'reason' => $this->rejectionReason,
|
||||
],
|
||||
'ip_address' => request()->ip(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
session()->flash('success', __('admin.booking_rejected'));
|
||||
$this->redirect(route('admin.bookings.pending'), navigate: true);
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'consultationHistory' => Consultation::query()
|
||||
->where('user_id', $this->consultation->user_id)
|
||||
->where('id', '!=', $this->consultation->id)
|
||||
->orderBy('booking_date', 'desc')
|
||||
->limit(5)
|
||||
->get(),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<flux:button href="{{ route('admin.bookings.pending') }}" variant="ghost" wire:navigate>
|
||||
<flux:icon name="arrow-left" class="w-4 h-4 rtl:rotate-180" />
|
||||
{{ __('common.back') }}
|
||||
</flux:button>
|
||||
<flux:heading size="xl">{{ __('admin.review_booking') }}</flux:heading>
|
||||
</div>
|
||||
|
||||
@if(session('error'))
|
||||
<flux:callout variant="danger" class="mb-6">
|
||||
{{ session('error') }}
|
||||
</flux:callout>
|
||||
@endif
|
||||
|
||||
@if($consultation->status !== ConsultationStatus::Pending)
|
||||
<flux:callout variant="warning" class="mb-6">
|
||||
{{ __('admin.booking_already_processed_info', ['status' => $consultation->status->label()]) }}
|
||||
</flux:callout>
|
||||
@endif
|
||||
|
||||
<!-- Client Information -->
|
||||
<div class="bg-white dark:bg-zinc-800 rounded-lg p-6 border border-zinc-200 dark:border-zinc-700 mb-6">
|
||||
<flux:heading size="lg" class="mb-4">{{ __('admin.client_information') }}</flux:heading>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('admin.client_name') }}</p>
|
||||
<p class="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{{ $consultation->user?->full_name ?? __('common.unknown') }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('admin.client_email') }}</p>
|
||||
<p class="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{{ $consultation->user?->email ?? '-' }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('admin.client_phone') }}</p>
|
||||
<p class="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{{ $consultation->user?->phone ?? '-' }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('admin.client_type') }}</p>
|
||||
<p class="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{{ $consultation->user?->user_type?->value ?? '-' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Booking Details -->
|
||||
<div class="bg-white dark:bg-zinc-800 rounded-lg p-6 border border-zinc-200 dark:border-zinc-700 mb-6">
|
||||
<flux:heading size="lg" class="mb-4">{{ __('admin.booking_details') }}</flux:heading>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('admin.requested_date') }}</p>
|
||||
<p class="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{{ \Carbon\Carbon::parse($consultation->booking_date)->translatedFormat('l, d M Y') }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('admin.requested_time') }}</p>
|
||||
<p class="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{{ \Carbon\Carbon::parse($consultation->booking_time)->format('g:i A') }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('admin.submission_date') }}</p>
|
||||
<p class="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{{ $consultation->created_at->translatedFormat('d M Y, g:i A') }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('admin.current_status') }}</p>
|
||||
<flux:badge :variant="match($consultation->status) {
|
||||
ConsultationStatus::Pending => 'warning',
|
||||
ConsultationStatus::Approved => 'success',
|
||||
ConsultationStatus::Rejected => 'danger',
|
||||
default => 'default',
|
||||
}">
|
||||
{{ $consultation->status->label() }}
|
||||
</flux:badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400 mb-2">{{ __('admin.problem_summary') }}</p>
|
||||
<p class="text-zinc-900 dark:text-zinc-100 whitespace-pre-wrap">
|
||||
{{ $consultation->problem_summary }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Consultation History -->
|
||||
@if($consultationHistory->count() > 0)
|
||||
<div class="bg-white dark:bg-zinc-800 rounded-lg p-6 border border-zinc-200 dark:border-zinc-700 mb-6">
|
||||
<flux:heading size="lg" class="mb-4">{{ __('admin.consultation_history') }}</flux:heading>
|
||||
|
||||
<div class="space-y-3">
|
||||
@foreach($consultationHistory as $history)
|
||||
<div class="flex items-center justify-between p-3 bg-zinc-50 dark:bg-zinc-700 rounded-lg">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900 dark:text-zinc-100">
|
||||
{{ \Carbon\Carbon::parse($history->booking_date)->translatedFormat('d M Y') }}
|
||||
</p>
|
||||
<p class="text-sm text-zinc-500 dark:text-zinc-400">
|
||||
{{ $history->consultation_type?->value ?? '-' }}
|
||||
</p>
|
||||
</div>
|
||||
<flux:badge :variant="match($history->status) {
|
||||
ConsultationStatus::Pending => 'warning',
|
||||
ConsultationStatus::Approved => 'success',
|
||||
ConsultationStatus::Completed => 'default',
|
||||
ConsultationStatus::Rejected => 'danger',
|
||||
ConsultationStatus::Cancelled => 'danger',
|
||||
ConsultationStatus::NoShow => 'danger',
|
||||
}" size="sm">
|
||||
{{ $history->status->label() }}
|
||||
</flux:badge>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Action Buttons -->
|
||||
@if($consultation->status === ConsultationStatus::Pending)
|
||||
<div class="flex gap-4">
|
||||
<flux:button wire:click="openApproveModal" variant="primary">
|
||||
{{ __('admin.approve') }}
|
||||
</flux:button>
|
||||
<flux:button wire:click="openRejectModal" variant="danger">
|
||||
{{ __('admin.reject') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Approve Modal -->
|
||||
<flux:modal wire:model="showApproveModal">
|
||||
<div class="space-y-6">
|
||||
<flux:heading size="lg">{{ __('admin.approve_booking') }}</flux:heading>
|
||||
|
||||
<!-- Client Info Summary -->
|
||||
<div class="bg-zinc-50 dark:bg-zinc-700 p-4 rounded-lg">
|
||||
<p><strong>{{ __('admin.client') }}:</strong> {{ $consultation->user?->full_name }}</p>
|
||||
<p><strong>{{ __('admin.date') }}:</strong> {{ \Carbon\Carbon::parse($consultation->booking_date)->translatedFormat('l, d M Y') }}</p>
|
||||
<p><strong>{{ __('admin.time') }}:</strong> {{ \Carbon\Carbon::parse($consultation->booking_time)->format('g:i A') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Consultation Type -->
|
||||
<flux:field>
|
||||
<flux:label>{{ __('admin.consultation_type') }}</flux:label>
|
||||
<flux:radio.group wire:model.live="consultationType">
|
||||
<flux:radio value="free" label="{{ __('admin.free_consultation') }}" />
|
||||
<flux:radio value="paid" label="{{ __('admin.paid_consultation') }}" />
|
||||
</flux:radio.group>
|
||||
</flux:field>
|
||||
|
||||
<!-- Payment Amount (if paid) -->
|
||||
@if($consultationType === 'paid')
|
||||
<flux:field>
|
||||
<flux:label>{{ __('admin.payment_amount') }} *</flux:label>
|
||||
<flux:input
|
||||
type="number"
|
||||
wire:model="paymentAmount"
|
||||
step="0.01"
|
||||
min="0"
|
||||
/>
|
||||
<flux:error name="paymentAmount" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('admin.payment_instructions') }}</flux:label>
|
||||
<flux:textarea
|
||||
wire:model="paymentInstructions"
|
||||
rows="3"
|
||||
placeholder="{{ __('admin.payment_instructions_placeholder') }}"
|
||||
/>
|
||||
</flux:field>
|
||||
@endif
|
||||
|
||||
<div class="flex gap-3 justify-end">
|
||||
<flux:button wire:click="$set('showApproveModal', false)">
|
||||
{{ __('common.cancel') }}
|
||||
</flux:button>
|
||||
<flux:button variant="primary" wire:click="approve">
|
||||
{{ __('admin.approve') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</flux:modal>
|
||||
|
||||
<!-- Reject Modal -->
|
||||
<flux:modal wire:model="showRejectModal">
|
||||
<div class="space-y-6">
|
||||
<flux:heading size="lg">{{ __('admin.reject_booking') }}</flux:heading>
|
||||
|
||||
<!-- Client Info Summary -->
|
||||
<div class="bg-zinc-50 dark:bg-zinc-700 p-4 rounded-lg">
|
||||
<p><strong>{{ __('admin.client') }}:</strong> {{ $consultation->user?->full_name }}</p>
|
||||
<p><strong>{{ __('admin.date') }}:</strong> {{ \Carbon\Carbon::parse($consultation->booking_date)->translatedFormat('l, d M Y') }}</p>
|
||||
<p><strong>{{ __('admin.time') }}:</strong> {{ \Carbon\Carbon::parse($consultation->booking_time)->format('g:i A') }}</p>
|
||||
</div>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('admin.rejection_reason') }} ({{ __('common.optional') }})</flux:label>
|
||||
<flux:textarea
|
||||
wire:model="rejectionReason"
|
||||
rows="3"
|
||||
placeholder="{{ __('admin.rejection_reason_placeholder') }}"
|
||||
/>
|
||||
<flux:error name="rejectionReason" />
|
||||
</flux:field>
|
||||
|
||||
<div class="flex gap-3 justify-end">
|
||||
<flux:button wire:click="$set('showRejectModal', false)">
|
||||
{{ __('common.cancel') }}
|
||||
</flux:button>
|
||||
<flux:button variant="danger" wire:click="reject">
|
||||
{{ __('admin.reject') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</flux:modal>
|
||||
</div>
|
||||
Reference in New Issue
Block a user