comlete story 2.2 with qa test & updated architect files
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\UserStatus;
|
||||
use App\Enums\UserType;
|
||||
use App\Models\AdminLog;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
public string $company_name = '';
|
||||
public string $company_cert_number = '';
|
||||
public string $contact_person_name = '';
|
||||
public string $contact_person_id = '';
|
||||
public string $email = '';
|
||||
public string $phone = '';
|
||||
public string $password = '';
|
||||
public string $preferred_language = 'ar';
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'company_name' => ['required', 'string', 'max:255'],
|
||||
'company_cert_number' => ['required', 'string', 'max:100', 'unique:users,company_cert_number'],
|
||||
'contact_person_name' => ['required', 'string', 'max:255'],
|
||||
'contact_person_id' => ['required', 'string', 'max:50'],
|
||||
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
|
||||
'phone' => ['required', 'string', 'max:20'],
|
||||
'password' => ['required', 'string', 'min:8'],
|
||||
'preferred_language' => ['required', 'in:ar,en'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'company_cert_number.unique' => __('clients.registration_number_exists'),
|
||||
'email.unique' => __('clients.email_exists'),
|
||||
];
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
$validated = $this->validate();
|
||||
|
||||
$user = User::create([
|
||||
'user_type' => UserType::Company,
|
||||
'full_name' => $validated['company_name'],
|
||||
'company_name' => $validated['company_name'],
|
||||
'company_cert_number' => $validated['company_cert_number'],
|
||||
'contact_person_name' => $validated['contact_person_name'],
|
||||
'contact_person_id' => $validated['contact_person_id'],
|
||||
'email' => $validated['email'],
|
||||
'phone' => $validated['phone'],
|
||||
'password' => Hash::make($validated['password']),
|
||||
'preferred_language' => $validated['preferred_language'],
|
||||
'status' => UserStatus::Active,
|
||||
]);
|
||||
|
||||
AdminLog::create([
|
||||
'admin_id' => auth()->id(),
|
||||
'action' => 'create',
|
||||
'target_type' => 'user',
|
||||
'target_id' => $user->id,
|
||||
'new_values' => $user->only(['company_name', 'email', 'company_cert_number', 'contact_person_name', 'phone', 'preferred_language']),
|
||||
'ip_address' => request()->ip(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
session()->flash('success', __('clients.company_created'));
|
||||
|
||||
$this->redirect(route('admin.clients.company.index'), navigate: true);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-6">
|
||||
<flux:button variant="ghost" :href="route('admin.clients.company.index')" wire:navigate icon="arrow-left">
|
||||
{{ __('clients.back_to_companies') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<flux:heading size="xl">{{ __('clients.create_company') }}</flux:heading>
|
||||
<flux:text class="mt-1 text-zinc-500 dark:text-zinc-400">{{ __('clients.company_clients') }}</flux:text>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<form wire:submit="create" class="space-y-6">
|
||||
<div class="grid gap-6 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.company_name') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="company_name"
|
||||
type="text"
|
||||
required
|
||||
autofocus
|
||||
/>
|
||||
<flux:error name="company_name" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.registration_number') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="company_cert_number"
|
||||
type="text"
|
||||
required
|
||||
/>
|
||||
<flux:error name="company_cert_number" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.contact_person_name') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="contact_person_name"
|
||||
type="text"
|
||||
required
|
||||
/>
|
||||
<flux:error name="contact_person_name" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.contact_person_id') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="contact_person_id"
|
||||
type="text"
|
||||
required
|
||||
/>
|
||||
<flux:error name="contact_person_id" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.email') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="email"
|
||||
type="email"
|
||||
required
|
||||
/>
|
||||
<flux:error name="email" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.phone') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="phone"
|
||||
type="tel"
|
||||
required
|
||||
/>
|
||||
<flux:error name="phone" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.password') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="password"
|
||||
type="password"
|
||||
required
|
||||
/>
|
||||
<flux:error name="password" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.preferred_language') }} *</flux:label>
|
||||
<flux:select wire:model="preferred_language" required>
|
||||
<flux:select.option value="ar">{{ __('clients.arabic') }}</flux:select.option>
|
||||
<flux:select.option value="en">{{ __('clients.english') }}</flux:select.option>
|
||||
</flux:select>
|
||||
<flux:error name="preferred_language" />
|
||||
</flux:field>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-4 border-t border-zinc-200 pt-6 dark:border-zinc-700">
|
||||
<flux:button variant="ghost" :href="route('admin.clients.company.index')" wire:navigate>
|
||||
{{ __('clients.cancel') }}
|
||||
</flux:button>
|
||||
<flux:button variant="primary" type="submit">
|
||||
{{ __('clients.create') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\UserStatus;
|
||||
use App\Models\AdminLog;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
public User $client;
|
||||
|
||||
public string $company_name = '';
|
||||
public string $company_cert_number = '';
|
||||
public string $contact_person_name = '';
|
||||
public string $contact_person_id = '';
|
||||
public string $email = '';
|
||||
public string $phone = '';
|
||||
public string $password = '';
|
||||
public string $preferred_language = '';
|
||||
public string $status = '';
|
||||
|
||||
public function mount(User $client): void
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->company_name = $client->company_name ?? '';
|
||||
$this->company_cert_number = $client->company_cert_number ?? '';
|
||||
$this->contact_person_name = $client->contact_person_name ?? '';
|
||||
$this->contact_person_id = $client->contact_person_id ?? '';
|
||||
$this->email = $client->email;
|
||||
$this->phone = $client->phone ?? '';
|
||||
$this->preferred_language = $client->preferred_language ?? 'ar';
|
||||
$this->status = $client->status->value;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'company_name' => ['required', 'string', 'max:255'],
|
||||
'company_cert_number' => ['required', 'string', 'max:100', Rule::unique('users', 'company_cert_number')->ignore($this->client->id)],
|
||||
'contact_person_name' => ['required', 'string', 'max:255'],
|
||||
'contact_person_id' => ['required', 'string', 'max:50'],
|
||||
'email' => ['required', 'email', 'max:255', Rule::unique('users', 'email')->ignore($this->client->id)],
|
||||
'phone' => ['required', 'string', 'max:20'],
|
||||
'password' => ['nullable', 'string', 'min:8'],
|
||||
'preferred_language' => ['required', 'in:ar,en'],
|
||||
'status' => ['required', 'in:active,deactivated'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'company_cert_number.unique' => __('clients.registration_number_exists'),
|
||||
'email.unique' => __('clients.email_exists'),
|
||||
];
|
||||
}
|
||||
|
||||
public function update(): void
|
||||
{
|
||||
$validated = $this->validate();
|
||||
|
||||
$oldValues = $this->client->only(['company_name', 'email', 'company_cert_number', 'contact_person_name', 'contact_person_id', 'phone', 'preferred_language', 'status']);
|
||||
|
||||
$this->client->company_name = $validated['company_name'];
|
||||
$this->client->full_name = $validated['company_name'];
|
||||
$this->client->company_cert_number = $validated['company_cert_number'];
|
||||
$this->client->contact_person_name = $validated['contact_person_name'];
|
||||
$this->client->contact_person_id = $validated['contact_person_id'];
|
||||
$this->client->email = $validated['email'];
|
||||
$this->client->phone = $validated['phone'];
|
||||
$this->client->preferred_language = $validated['preferred_language'];
|
||||
$this->client->status = $validated['status'];
|
||||
|
||||
if (! empty($validated['password'])) {
|
||||
$this->client->password = Hash::make($validated['password']);
|
||||
}
|
||||
|
||||
$this->client->save();
|
||||
|
||||
AdminLog::create([
|
||||
'admin_id' => auth()->id(),
|
||||
'action' => 'update',
|
||||
'target_type' => 'user',
|
||||
'target_id' => $this->client->id,
|
||||
'old_values' => $oldValues,
|
||||
'new_values' => $this->client->only(['company_name', 'email', 'company_cert_number', 'contact_person_name', 'contact_person_id', 'phone', 'preferred_language', 'status']),
|
||||
'ip_address' => request()->ip(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
session()->flash('success', __('clients.company_updated'));
|
||||
|
||||
$this->redirect(route('admin.clients.company.index'), navigate: true);
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'statuses' => UserStatus::cases(),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-6">
|
||||
<flux:button variant="ghost" :href="route('admin.clients.company.index')" wire:navigate icon="arrow-left">
|
||||
{{ __('clients.back_to_companies') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<flux:heading size="xl">{{ __('clients.edit_company') }}</flux:heading>
|
||||
<flux:text class="mt-1 text-zinc-500 dark:text-zinc-400">{{ $client->company_name }}</flux:text>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-zinc-200 bg-white p-6 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<form wire:submit="update" class="space-y-6">
|
||||
<div class="grid gap-6 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.company_name') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="company_name"
|
||||
type="text"
|
||||
required
|
||||
autofocus
|
||||
/>
|
||||
<flux:error name="company_name" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.registration_number') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="company_cert_number"
|
||||
type="text"
|
||||
required
|
||||
/>
|
||||
<flux:error name="company_cert_number" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.contact_person_name') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="contact_person_name"
|
||||
type="text"
|
||||
required
|
||||
/>
|
||||
<flux:error name="contact_person_name" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.contact_person_id') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="contact_person_id"
|
||||
type="text"
|
||||
required
|
||||
/>
|
||||
<flux:error name="contact_person_id" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.email') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="email"
|
||||
type="email"
|
||||
required
|
||||
/>
|
||||
<flux:error name="email" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.phone') }} *</flux:label>
|
||||
<flux:input
|
||||
wire:model="phone"
|
||||
type="tel"
|
||||
required
|
||||
/>
|
||||
<flux:error name="phone" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.password') }}</flux:label>
|
||||
<flux:input
|
||||
wire:model="password"
|
||||
type="password"
|
||||
placeholder="{{ __('Leave blank to keep current password') }}"
|
||||
/>
|
||||
<flux:error name="password" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.preferred_language') }} *</flux:label>
|
||||
<flux:select wire:model="preferred_language" required>
|
||||
<flux:select.option value="ar">{{ __('clients.arabic') }}</flux:select.option>
|
||||
<flux:select.option value="en">{{ __('clients.english') }}</flux:select.option>
|
||||
</flux:select>
|
||||
<flux:error name="preferred_language" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('clients.status') }} *</flux:label>
|
||||
<flux:select wire:model="status" required>
|
||||
@foreach ($statuses as $statusOption)
|
||||
<flux:select.option value="{{ $statusOption->value }}">
|
||||
{{ __('clients.' . $statusOption->value) }}
|
||||
</flux:select.option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
<flux:error name="status" />
|
||||
</flux:field>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-4 border-t border-zinc-200 pt-6 dark:border-zinc-700">
|
||||
<flux:button variant="ghost" :href="route('admin.clients.company.index')" wire:navigate>
|
||||
{{ __('clients.cancel') }}
|
||||
</flux:button>
|
||||
<flux:button variant="primary" type="submit">
|
||||
{{ __('clients.update') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\UserStatus;
|
||||
use App\Models\User;
|
||||
use Livewire\Volt\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
new class extends Component {
|
||||
use WithPagination;
|
||||
|
||||
public string $search = '';
|
||||
public string $statusFilter = '';
|
||||
public int $perPage = 10;
|
||||
|
||||
public function updatedSearch(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function updatedStatusFilter(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function updatedPerPage(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function clearFilters(): void
|
||||
{
|
||||
$this->search = '';
|
||||
$this->statusFilter = '';
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
return [
|
||||
'clients' => User::companies()
|
||||
->when($this->search, fn ($q) => $q->where(function ($q) {
|
||||
$q->where('company_name', 'like', "%{$this->search}%")
|
||||
->orWhere('email', 'like', "%{$this->search}%")
|
||||
->orWhere('company_cert_number', 'like', "%{$this->search}%");
|
||||
}))
|
||||
->when($this->statusFilter, fn ($q) => $q->where('status', $this->statusFilter))
|
||||
->latest()
|
||||
->paginate($this->perPage),
|
||||
'statuses' => UserStatus::cases(),
|
||||
];
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ __('clients.company_clients') }}</flux:heading>
|
||||
<flux:text class="mt-1 text-zinc-500 dark:text-zinc-400">{{ __('clients.clients') }}</flux:text>
|
||||
</div>
|
||||
<flux:button variant="primary" :href="route('admin.clients.company.create')" wire:navigate icon="plus">
|
||||
{{ __('clients.create_company') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 rounded-lg border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-end">
|
||||
<div class="flex-1">
|
||||
<flux:input
|
||||
wire:model.live.debounce.300ms="search"
|
||||
:placeholder="__('clients.search_company_placeholder')"
|
||||
icon="magnifying-glass"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full sm:w-48">
|
||||
<flux:select wire:model.live="statusFilter">
|
||||
<flux:select.option value="">{{ __('clients.all_statuses') }}</flux:select.option>
|
||||
@foreach ($statuses as $status)
|
||||
<flux:select.option value="{{ $status->value }}">
|
||||
{{ __('clients.' . $status->value) }}
|
||||
</flux:select.option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
</div>
|
||||
<div class="w-full sm:w-32">
|
||||
<flux:select wire:model.live="perPage">
|
||||
<flux:select.option value="10">10 {{ __('clients.per_page') }}</flux:select.option>
|
||||
<flux:select.option value="25">25 {{ __('clients.per_page') }}</flux:select.option>
|
||||
<flux:select.option value="50">50 {{ __('clients.per_page') }}</flux:select.option>
|
||||
</flux:select>
|
||||
</div>
|
||||
@if ($search || $statusFilter)
|
||||
<flux:button wire:click="clearFilters" variant="ghost" icon="x-mark">
|
||||
{{ __('clients.clear_filters') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded-lg border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-zinc-200 dark:divide-zinc-700">
|
||||
<thead class="bg-zinc-50 dark:bg-zinc-900">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-start text-xs font-medium uppercase tracking-wider text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('clients.company_name') }}
|
||||
</th>
|
||||
<th class="px-6 py-3 text-start text-xs font-medium uppercase tracking-wider text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('clients.contact_person') }}
|
||||
</th>
|
||||
<th class="px-6 py-3 text-start text-xs font-medium uppercase tracking-wider text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('clients.email') }}
|
||||
</th>
|
||||
<th class="px-6 py-3 text-start text-xs font-medium uppercase tracking-wider text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('clients.registration_number') }}
|
||||
</th>
|
||||
<th class="px-6 py-3 text-start text-xs font-medium uppercase tracking-wider text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('clients.status') }}
|
||||
</th>
|
||||
<th class="px-6 py-3 text-start text-xs font-medium uppercase tracking-wider text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('clients.created_at') }}
|
||||
</th>
|
||||
<th class="px-6 py-3 text-end text-xs font-medium uppercase tracking-wider text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('clients.actions') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-zinc-200 bg-white dark:divide-zinc-700 dark:bg-zinc-800">
|
||||
@forelse ($clients as $client)
|
||||
<tr wire:key="client-{{ $client->id }}">
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<flux:avatar size="sm" :name="$client->company_name" />
|
||||
<span class="font-medium text-zinc-900 dark:text-zinc-100">{{ $client->company_name }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-zinc-600 dark:text-zinc-400">
|
||||
{{ $client->contact_person_name }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-zinc-600 dark:text-zinc-400">
|
||||
{{ $client->email }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-zinc-600 dark:text-zinc-400">
|
||||
{{ $client->company_cert_number }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-6 py-4">
|
||||
@if ($client->status === UserStatus::Active)
|
||||
<flux:badge color="green" size="sm">{{ __('clients.active') }}</flux:badge>
|
||||
@else
|
||||
<flux:badge color="red" size="sm">{{ __('clients.deactivated') }}</flux:badge>
|
||||
@endif
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-zinc-600 dark:text-zinc-400">
|
||||
{{ $client->created_at->format('Y-m-d') }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-end">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<flux:button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
icon="eye"
|
||||
:href="route('admin.clients.company.show', $client)"
|
||||
wire:navigate
|
||||
:title="__('clients.view')"
|
||||
/>
|
||||
<flux:button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
icon="pencil"
|
||||
:href="route('admin.clients.company.edit', $client)"
|
||||
wire:navigate
|
||||
:title="__('clients.edit')"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="px-6 py-12 text-center">
|
||||
<div class="flex flex-col items-center">
|
||||
<flux:icon name="building-office" class="mb-4 h-12 w-12 text-zinc-400" />
|
||||
<flux:text class="text-zinc-500 dark:text-zinc-400">
|
||||
@if ($search || $statusFilter)
|
||||
{{ __('clients.no_companies_match') }}
|
||||
@else
|
||||
{{ __('clients.no_companies_found') }}
|
||||
@endif
|
||||
</flux:text>
|
||||
@if ($search || $statusFilter)
|
||||
<flux:button wire:click="clearFilters" variant="ghost" class="mt-4">
|
||||
{{ __('clients.clear_filters') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if ($clients->hasPages())
|
||||
<div class="border-t border-zinc-200 bg-zinc-50 px-6 py-4 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
{{ $clients->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ConsultationStatus;
|
||||
use App\Enums\TimelineStatus;
|
||||
use App\Enums\UserStatus;
|
||||
use App\Models\User;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
public User $client;
|
||||
|
||||
public function mount(User $client): void
|
||||
{
|
||||
$this->client = $client->loadCount([
|
||||
'consultations',
|
||||
'consultations as pending_consultations_count' => fn ($q) => $q->where('status', ConsultationStatus::Pending),
|
||||
'consultations as completed_consultations_count' => fn ($q) => $q->where('status', ConsultationStatus::Completed),
|
||||
'timelines',
|
||||
'timelines as active_timelines_count' => fn ($q) => $q->where('status', TimelineStatus::Active),
|
||||
]);
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div>
|
||||
<div class="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<flux:button variant="ghost" :href="route('admin.clients.company.index')" wire:navigate icon="arrow-left">
|
||||
{{ __('clients.back_to_companies') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
<flux:button variant="primary" :href="route('admin.clients.company.edit', $client)" wire:navigate icon="pencil">
|
||||
{{ __('clients.edit_company') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<flux:heading size="xl">{{ __('clients.company_profile') }}</flux:heading>
|
||||
<flux:text class="mt-1 text-zinc-500 dark:text-zinc-400">{{ $client->company_name }}</flux:text>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-3">
|
||||
{{-- Company Information --}}
|
||||
<div class="lg:col-span-2">
|
||||
<div class="rounded-lg border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<div class="border-b border-zinc-200 px-6 py-4 dark:border-zinc-700">
|
||||
<flux:heading size="lg">{{ __('clients.company_information') }}</flux:heading>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="flex items-start gap-6">
|
||||
<flux:avatar size="xl" :name="$client->company_name" />
|
||||
<div class="flex-1">
|
||||
<div class="grid gap-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<flux:text class="text-sm font-medium text-zinc-500 dark:text-zinc-400">{{ __('clients.company_name') }}</flux:text>
|
||||
<flux:text class="mt-1 text-zinc-900 dark:text-zinc-100">{{ $client->company_name }}</flux:text>
|
||||
</div>
|
||||
<div>
|
||||
<flux:text class="text-sm font-medium text-zinc-500 dark:text-zinc-400">{{ __('clients.registration_number') }}</flux:text>
|
||||
<flux:text class="mt-1 text-zinc-900 dark:text-zinc-100">{{ $client->company_cert_number }}</flux:text>
|
||||
</div>
|
||||
<div>
|
||||
<flux:text class="text-sm font-medium text-zinc-500 dark:text-zinc-400">{{ __('clients.contact_person_name') }}</flux:text>
|
||||
<flux:text class="mt-1 text-zinc-900 dark:text-zinc-100">{{ $client->contact_person_name }}</flux:text>
|
||||
</div>
|
||||
<div>
|
||||
<flux:text class="text-sm font-medium text-zinc-500 dark:text-zinc-400">{{ __('clients.contact_person_id') }}</flux:text>
|
||||
<flux:text class="mt-1 text-zinc-900 dark:text-zinc-100">{{ $client->contact_person_id }}</flux:text>
|
||||
</div>
|
||||
<div>
|
||||
<flux:text class="text-sm font-medium text-zinc-500 dark:text-zinc-400">{{ __('clients.email') }}</flux:text>
|
||||
<flux:text class="mt-1 text-zinc-900 dark:text-zinc-100">{{ $client->email }}</flux:text>
|
||||
</div>
|
||||
<div>
|
||||
<flux:text class="text-sm font-medium text-zinc-500 dark:text-zinc-400">{{ __('clients.phone') }}</flux:text>
|
||||
<flux:text class="mt-1 text-zinc-900 dark:text-zinc-100">{{ $client->phone }}</flux:text>
|
||||
</div>
|
||||
<div>
|
||||
<flux:text class="text-sm font-medium text-zinc-500 dark:text-zinc-400">{{ __('clients.preferred_language') }}</flux:text>
|
||||
<flux:text class="mt-1 text-zinc-900 dark:text-zinc-100">
|
||||
{{ $client->preferred_language === 'ar' ? __('clients.arabic') : __('clients.english') }}
|
||||
</flux:text>
|
||||
</div>
|
||||
<div>
|
||||
<flux:text class="text-sm font-medium text-zinc-500 dark:text-zinc-400">{{ __('clients.status') }}</flux:text>
|
||||
<div class="mt-1">
|
||||
@if ($client->status === UserStatus::Active)
|
||||
<flux:badge color="green">{{ __('clients.active') }}</flux:badge>
|
||||
@else
|
||||
<flux:badge color="red">{{ __('clients.deactivated') }}</flux:badge>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<flux:text class="text-sm font-medium text-zinc-500 dark:text-zinc-400">{{ __('clients.member_since') }}</flux:text>
|
||||
<flux:text class="mt-1 text-zinc-900 dark:text-zinc-100">{{ $client->created_at->format('Y-m-d') }}</flux:text>
|
||||
</div>
|
||||
<div>
|
||||
<flux:text class="text-sm font-medium text-zinc-500 dark:text-zinc-400">{{ __('clients.user_type') }}</flux:text>
|
||||
<flux:text class="mt-1 text-zinc-900 dark:text-zinc-100">{{ __('clients.company') }}</flux:text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Stats Sidebar --}}
|
||||
<div class="space-y-6">
|
||||
{{-- Consultation Summary --}}
|
||||
<div class="rounded-lg border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<div class="border-b border-zinc-200 px-6 py-4 dark:border-zinc-700">
|
||||
<flux:heading size="lg">{{ __('clients.consultation_history') }}</flux:heading>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('clients.total_consultations') }}</flux:text>
|
||||
<flux:badge color="zinc">{{ $client->consultations_count }}</flux:badge>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('clients.pending_consultations') }}</flux:text>
|
||||
<flux:badge color="yellow">{{ $client->pending_consultations_count }}</flux:badge>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('clients.completed_consultations') }}</flux:text>
|
||||
<flux:badge color="green">{{ $client->completed_consultations_count }}</flux:badge>
|
||||
</div>
|
||||
</div>
|
||||
@if ($client->consultations_count > 0)
|
||||
<div class="mt-4 border-t border-zinc-200 pt-4 dark:border-zinc-700">
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('clients.view_all_consultations') }}
|
||||
</flux:text>
|
||||
</div>
|
||||
@else
|
||||
<div class="mt-4 border-t border-zinc-200 pt-4 dark:border-zinc-700">
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('clients.no_consultations') }}
|
||||
</flux:text>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Timeline Summary --}}
|
||||
<div class="rounded-lg border border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<div class="border-b border-zinc-200 px-6 py-4 dark:border-zinc-700">
|
||||
<flux:heading size="lg">{{ __('clients.timeline_history') }}</flux:heading>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('clients.total_timelines') }}</flux:text>
|
||||
<flux:badge color="zinc">{{ $client->timelines_count }}</flux:badge>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('clients.active_timelines') }}</flux:text>
|
||||
<flux:badge color="blue">{{ $client->active_timelines_count }}</flux:badge>
|
||||
</div>
|
||||
</div>
|
||||
@if ($client->timelines_count > 0)
|
||||
<div class="mt-4 border-t border-zinc-200 pt-4 dark:border-zinc-700">
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('clients.view_all_timelines') }}
|
||||
</flux:text>
|
||||
</div>
|
||||
@else
|
||||
<div class="mt-4 border-t border-zinc-200 pt-4 dark:border-zinc-700">
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('clients.no_timelines') }}
|
||||
</flux:text>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user