Commit 20184e77 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add master-detail show pages for all major entities

Every entity list now has clickable rows that navigate to rich detail
pages with KPIs, tabbed content, and related data. New show pages:

- TrainerShow: groups, schedule, attendance, financial, qualifications
- EmployeeShow: personal info, team hierarchy, documents, timeline
- GroupShow: enrollments, schedule, sessions, attendance stats
- ProgramShow: groups, enrollments, settings, requirements
- FacilityShow: layouts, segments, reservations, schedule
- ProductShow: stock levels, movements, analytics
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent b1d89aa3
<?php
namespace App\Livewire\Facilities;
use App\Domain\Facility\Enums\ReservationStatus;
use App\Domain\Facility\Models\Facility;
use App\Domain\Facility\Models\SpaceReservation;
use Carbon\Carbon;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('تفاصيل المنشأة')]
class FacilityShow extends Component
{
public Facility $facility;
public string $activeTab = 'overview';
public function mount(Facility $facility): void
{
$this->authorize('facilities.list');
$this->facility = $facility->load([
'branch',
'layouts.segments',
'reservations' => fn ($q) => $q->where('status', '!=', ReservationStatus::Cancelled)
->where('reservation_date', '>=', now()->toDateString())
->orderBy('reservation_date')
->orderBy('start_time'),
'creator',
]);
}
public function render()
{
$today = Carbon::today();
$todayString = $today->toDateString();
// KPI calculations
$totalLayouts = $this->facility->layouts->count();
$totalSegments = $this->facility->layouts->sum(fn ($layout) => $layout->segments->count());
$todayReservations = SpaceReservation::where('facility_id', $this->facility->id)
->where('reservation_date', $todayString)
->where('status', ReservationStatus::Confirmed)
->get();
$activeReservationsToday = $todayReservations->count();
// Segments occupied today
$occupiedSegmentsToday = $todayReservations->flatMap(fn ($r) => $r->segment_ids ?? [])->unique()->count();
$utilizationPercent = $totalSegments > 0
? round(($occupiedSegmentsToday / $totalSegments) * 100, 1)
: 0;
// Upcoming reservations (next 7 days)
$upcomingReservations = SpaceReservation::where('facility_id', $this->facility->id)
->where('reservation_date', '>=', $todayString)
->where('reservation_date', '<=', $today->copy()->addDays(7)->toDateString())
->where('status', '!=', ReservationStatus::Cancelled)
->orderBy('reservation_date')
->orderBy('start_time')
->get();
// Today's schedule: reservations with time slots
$todaySchedule = SpaceReservation::where('facility_id', $this->facility->id)
->where('reservation_date', $todayString)
->where('status', '!=', ReservationStatus::Cancelled)
->orderBy('start_time')
->get();
return view('livewire.facilities.facility-show', [
'totalLayouts' => $totalLayouts,
'totalSegments' => $totalSegments,
'activeReservationsToday' => $activeReservationsToday,
'utilizationPercent' => $utilizationPercent,
'upcomingReservations' => $upcomingReservations,
'todaySchedule' => $todaySchedule,
]);
}
}
<?php
namespace App\Livewire\Groups;
use App\Domain\Attendance\Enums\AttendanceStatus;
use App\Domain\Attendance\Models\AttendanceRecord;
use App\Domain\Training\Enums\SessionStatus;
use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingSession;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('تفاصيل المجموعة')]
class GroupShow extends Component
{
public TrainingGroup $group;
public string $activeTab = 'overview';
public function mount(TrainingGroup $group): void
{
$this->authorize('groups.list');
$this->group = $group->load([
'program',
'branch',
'headTrainer',
'creator',
'schedules',
'schedules.facility',
'schedules.trainer',
]);
}
public function render()
{
// Active enrollments with participant details
$activeEnrollments = Enrollment::where('training_group_id', $this->group->id)
->where('status', 'active')
->with(['participant.person'])
->orderBy('enrollment_date')
->get();
// All enrollments count (any status)
$totalEnrollments = Enrollment::where('training_group_id', $this->group->id)
->whereNotIn('status', ['cancelled'])
->count();
// Sessions: recent completed + upcoming scheduled
$recentSessions = TrainingSession::where('training_group_id', $this->group->id)
->where('session_date', '<', now()->toDateString())
->orderByDesc('session_date')
->limit(20)
->get();
$upcomingSessions = TrainingSession::where('training_group_id', $this->group->id)
->where('session_date', '>=', now()->toDateString())
->orderBy('session_date')
->limit(10)
->get();
// Session stats
$totalSessions = TrainingSession::where('training_group_id', $this->group->id)->count();
$completedSessions = TrainingSession::where('training_group_id', $this->group->id)
->where('status', SessionStatus::Completed)
->count();
// Attendance stats for the group (across all sessions)
$sessionIds = TrainingSession::where('training_group_id', $this->group->id)
->pluck('id');
$attendanceQuery = AttendanceRecord::whereIn('training_session_id', $sessionIds);
$totalAttendanceRecords = (clone $attendanceQuery)->count();
$presentCount = (clone $attendanceQuery)->where('status', AttendanceStatus::Present)->count();
$lateCount = (clone $attendanceQuery)->where('status', AttendanceStatus::Late)->count();
$absentCount = (clone $attendanceQuery)->whereIn('status', [AttendanceStatus::Absent, AttendanceStatus::NoShow])->count();
$partialCount = (clone $attendanceQuery)->where('status', AttendanceStatus::Partial)->count();
$excusedCount = (clone $attendanceQuery)->where('status', AttendanceStatus::Excused)->count();
$cancelledExempt = (clone $attendanceQuery)->whereIn('status', [AttendanceStatus::Cancelled, AttendanceStatus::Exempt])->count();
$attendanceDenominator = $totalAttendanceRecords - $cancelledExempt;
$attendanceRate = $attendanceDenominator > 0
? round(($presentCount + $lateCount + $partialCount) / $attendanceDenominator * 100, 1)
: 0;
// Capacity percentage
$capacityPercent = $this->group->max_capacity > 0
? round(($this->group->current_count / $this->group->max_capacity) * 100, 1)
: 0;
// Per-participant attendance rates for enrollments tab
$participantAttendanceRates = [];
if ($activeEnrollments->isNotEmpty() && $sessionIds->isNotEmpty()) {
$participantClass = \App\Domain\Participant\Models\Participant::class;
foreach ($activeEnrollments as $enrollment) {
$pQuery = AttendanceRecord::whereIn('training_session_id', $sessionIds)
->where('subject_type', $participantClass)
->where('subject_id', $enrollment->participant_id);
$pTotal = (clone $pQuery)->count();
$pExcluded = (clone $pQuery)->whereIn('status', [AttendanceStatus::Cancelled, AttendanceStatus::Exempt])->count();
$pPositive = (clone $pQuery)->whereIn('status', [AttendanceStatus::Present, AttendanceStatus::Late, AttendanceStatus::Partial])->count();
$pDenom = $pTotal - $pExcluded;
$participantAttendanceRates[$enrollment->participant_id] = $pDenom > 0
? round(($pPositive / $pDenom) * 100, 1)
: null;
}
}
// Waitlist count
$waitlistCount = $this->group->waitlist_count ?? 0;
return view('livewire.groups.group-show', [
'activeEnrollments' => $activeEnrollments,
'totalEnrollments' => $totalEnrollments,
'recentSessions' => $recentSessions,
'upcomingSessions' => $upcomingSessions,
'totalSessions' => $totalSessions,
'completedSessions' => $completedSessions,
'totalAttendanceRecords' => $totalAttendanceRecords,
'presentCount' => $presentCount,
'lateCount' => $lateCount,
'absentCount' => $absentCount,
'partialCount' => $partialCount,
'excusedCount' => $excusedCount,
'attendanceRate' => $attendanceRate,
'capacityPercent' => $capacityPercent,
'participantAttendanceRates' => $participantAttendanceRates,
'waitlistCount' => $waitlistCount,
]);
}
}
<?php
namespace App\Livewire\HR;
use App\Domain\HR\Models\Employee;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('ملف الموظف')]
class EmployeeShow extends Component
{
public Employee $employee;
public string $activeTab = 'overview';
public function mount(Employee $employee): void
{
$this->authorize('employees.list');
$this->employee = $employee->load([
'person',
'user',
'branch',
'manager.person',
'directReports.person',
'trainer',
'creator',
'documents',
]);
}
public function render()
{
$person = $this->employee->person;
// Tenure calculation
$tenureYears = 0;
$tenureMonths = 0;
if ($this->employee->start_date) {
$endDate = $this->employee->end_date ?? now();
$tenureYears = $this->employee->start_date->diffInYears($endDate);
$tenureMonths = $this->employee->start_date->diffInMonths($endDate) % 12;
}
// Direct reports count
$directReportsCount = $this->employee->directReports->count();
// Documents count
$documentsCount = $this->employee->documents->count();
// Monthly salary equivalent (convert based on frequency)
$monthlySalary = $this->calculateMonthlySalary();
// Is trainer
$isTrainer = $this->employee->trainer !== null;
return view('livewire.hr.employee-show', [
'person' => $person,
'tenureYears' => $tenureYears,
'tenureMonths' => $tenureMonths,
'directReportsCount' => $directReportsCount,
'documentsCount' => $documentsCount,
'monthlySalary' => $monthlySalary,
'isTrainer' => $isTrainer,
]);
}
private function calculateMonthlySalary(): int
{
$salary = $this->employee->salary_amount ?? 0;
$frequency = $this->employee->salary_frequency;
if (!$frequency) {
return $salary;
}
return match ($frequency->value) {
'monthly' => $salary,
'biweekly' => (int) round($salary * 2),
'weekly' => (int) round($salary * 4.33),
'daily' => (int) round($salary * 30),
'hourly' => (int) round($salary * ($this->employee->working_hours_per_week ?? 40) * 4.33),
default => $salary,
};
}
}
<?php
namespace App\Livewire\HR;
use App\Domain\Attendance\Enums\AttendanceStatus;
use App\Domain\Attendance\Models\AttendanceRecord;
use App\Domain\HR\Models\Trainer;
use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingSession;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('ملف المدرب')]
class TrainerShow extends Component
{
public Trainer $trainer;
public string $activeTab = 'overview';
public function mount(Trainer $trainer): void
{
$this->authorize('trainers.list');
$this->trainer = $trainer->load([
'employee.person',
'employee.branch',
'person',
'qualifications',
'availabilities',
'compensations',
]);
}
public function render()
{
$person = $this->trainer->employee?->person ?? $this->trainer->person;
$trainerUserId = $this->trainer->employee?->user_id;
// Groups where this trainer is head trainer
$groups = TrainingGroup::where('head_trainer_id', $trainerUserId)
->with(['program', 'schedules', 'branch'])
->withCount(['enrollments as active_enrollments_count' => function ($q) {
$q->where('status', 'active');
}])
->get();
// Upcoming sessions (next 7 days)
$upcomingSessions = TrainingSession::whereHas('group', function ($q) use ($trainerUserId) {
$q->where('head_trainer_id', $trainerUserId);
})
->where('session_date', '>=', now()->toDateString())
->where('session_date', '<=', now()->addDays(7)->toDateString())
->with(['group'])
->orderBy('session_date')
->orderBy('start_time')
->limit(20)
->get();
// Attendance stats for this trainer (as subject)
$trainerClass = Trainer::class;
$attendanceQuery = AttendanceRecord::where('subject_type', $trainerClass)
->where('subject_id', $this->trainer->id);
$totalSessionsAttended = (clone $attendanceQuery)->count();
$presentCount = (clone $attendanceQuery)->where('status', AttendanceStatus::Present)->count();
$lateCount = (clone $attendanceQuery)->where('status', AttendanceStatus::Late)->count();
$absentCount = (clone $attendanceQuery)->whereIn('status', [AttendanceStatus::Absent, AttendanceStatus::NoShow])->count();
$cancelledExempt = (clone $attendanceQuery)->whereIn('status', [AttendanceStatus::Cancelled, AttendanceStatus::Exempt])->count();
$attendanceDenominator = $totalSessionsAttended - $cancelledExempt;
$attendanceRate = $attendanceDenominator > 0
? round(($presentCount + $lateCount) / $attendanceDenominator * 100, 1)
: 0;
// Total participants across all groups
$totalParticipants = $groups->sum('active_enrollments_count');
// Capacity utilization
$totalCapacity = $groups->sum('max_capacity');
$capacityRate = $totalCapacity > 0
? round($totalParticipants / $totalCapacity * 100, 1)
: 0;
// Recent compensations
$recentCompensations = $this->trainer->compensations()
->orderByDesc('period_start')
->limit(6)
->get();
// Monthly earnings (current month)
$monthlyEarnings = $this->trainer->compensations()
->where('period_start', '>=', now()->startOfMonth())
->where('period_start', '<=', now()->endOfMonth())
->sum('total_amount');
// Advances balance
$pendingAdvances = $this->trainer->advances()
->where('status', 'pending')
->sum('amount');
return view('livewire.hr.trainer-show', [
'person' => $person,
'groups' => $groups,
'upcomingSessions' => $upcomingSessions,
'totalSessionsAttended' => $totalSessionsAttended,
'presentCount' => $presentCount,
'lateCount' => $lateCount,
'absentCount' => $absentCount,
'attendanceRate' => $attendanceRate,
'totalParticipants' => $totalParticipants,
'capacityRate' => $capacityRate,
'recentCompensations' => $recentCompensations,
'monthlyEarnings' => $monthlyEarnings,
'pendingAdvances' => $pendingAdvances,
]);
}
}
<?php
namespace App\Livewire\Inventory;
use App\Domain\Inventory\Enums\MovementDirection;
use App\Domain\Inventory\Models\InventoryMovement;
use App\Domain\Inventory\Models\Product;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('تفاصيل المنتج')]
class ProductShow extends Component
{
public Product $product;
public function mount(Product $product): void
{
$this->authorize('inventory.list');
$this->product = $product->load(['category', 'inventoryLevels.warehouse', 'branch']);
}
public function render()
{
$levels = $this->product->inventoryLevels;
$totalOnHand = $levels->sum('quantity_on_hand');
$totalReserved = $levels->sum('quantity_reserved');
$totalAvailable = $levels->sum('quantity_available');
$movements = InventoryMovement::where('product_id', $this->product->id)
->with('warehouse')
->orderByDesc('created_at')
->limit(30)
->get();
// Analytics
$totalIn = InventoryMovement::where('product_id', $this->product->id)
->where('direction', MovementDirection::In->value)
->sum('quantity');
$totalSold = InventoryMovement::where('product_id', $this->product->id)
->where('direction', MovementDirection::Out->value)
->where('movement_type', 'sale')
->sum('quantity');
$firstMovement = InventoryMovement::where('product_id', $this->product->id)
->orderBy('created_at')
->value('created_at');
$monthsActive = 1;
if ($firstMovement) {
$monthsActive = max(1, (int) now()->diffInMonths($firstMovement));
}
$totalMovements = InventoryMovement::where('product_id', $this->product->id)->sum('quantity');
$avgMonthlyMovement = round($totalMovements / $monthsActive, 1);
// Profit margin
$margin = 0;
if ($this->product->selling_price > 0 && $this->product->cost_price > 0) {
$margin = round((($this->product->selling_price - $this->product->cost_price) / $this->product->selling_price) * 100, 1);
}
return view('livewire.inventory.product-show', [
'levels' => $levels,
'totalOnHand' => $totalOnHand,
'totalReserved' => $totalReserved,
'totalAvailable' => $totalAvailable,
'movements' => $movements,
'totalIn' => $totalIn,
'totalSold' => $totalSold,
'avgMonthlyMovement' => $avgMonthlyMovement,
'margin' => $margin,
]);
}
}
<?php
namespace App\Livewire\Programs;
use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingProgram;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('تفاصيل البرنامج')]
class ProgramShow extends Component
{
public TrainingProgram $program;
public string $activeTab = 'overview';
public function mount(TrainingProgram $program): void
{
$this->authorize('programs.list');
$this->program = $program->load([
'activity',
'branch',
'defaultTrainer',
'creator',
'groups.headTrainer',
'groups.schedules',
'enrollments.participant.person',
'enrollments.group',
]);
}
public function render()
{
$groups = $this->program->groups;
$enrollments = $this->program->enrollments;
// KPI calculations
$totalGroups = $groups->count();
$activeEnrollments = $enrollments->where('status.value', 'active')->count();
$totalCapacity = $groups->sum('max_capacity');
$fillRate = $totalCapacity > 0 ? round(($activeEnrollments / $totalCapacity) * 100, 1) : 0;
$waitlistTotal = $groups->sum('waitlist_count');
// Total revenue from enrollment invoices
$totalRevenue = Enrollment::where('training_program_id', $this->program->id)
->whereNotNull('invoice_id')
->with('invoice')
->get()
->sum(fn ($e) => $e->invoice?->total_amount ?? 0);
// Recent enrollments (last 20)
$recentEnrollments = $enrollments->sortByDesc('enrollment_date')->take(20);
return view('livewire.programs.program-show', [
'groups' => $groups,
'totalGroups' => $totalGroups,
'activeEnrollments' => $activeEnrollments,
'totalCapacity' => $totalCapacity,
'fillRate' => $fillRate,
'waitlistTotal' => $waitlistTotal,
'totalRevenue' => $totalRevenue,
'recentEnrollments' => $recentEnrollments,
]);
}
}
...@@ -67,10 +67,10 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin ...@@ -67,10 +67,10 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin
</thead> </thead>
<tbody class="divide-y divide-gray-100"> <tbody class="divide-y divide-gray-100">
@forelse($facilities as $facility) @forelse($facilities as $facility)
<tr class="hover:bg-gray-50"> <tr class="hover:bg-gray-50 cursor-pointer" onclick="window.location='{{ route('facilities.show', $facility) }}'">
<td class="px-4 py-3"> <td class="px-4 py-3">
<div> <div>
<p class="font-medium text-gray-800">{{ $facility->name_ar }}</p> <a href="{{ route('facilities.show', $facility) }}" wire:navigate class="font-medium text-gray-800 hover:text-blue-600">{{ $facility->name_ar }}</a>
@if($facility->code) @if($facility->code)
<p class="text-xs text-gray-400" dir="ltr">{{ $facility->code }}</p> <p class="text-xs text-gray-400" dir="ltr">{{ $facility->code }}</p>
@endif @endif
......
This diff is collapsed.
...@@ -63,10 +63,10 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin ...@@ -63,10 +63,10 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin
</thead> </thead>
<tbody class="divide-y divide-gray-100"> <tbody class="divide-y divide-gray-100">
@forelse($groups as $group) @forelse($groups as $group)
<tr class="hover:bg-gray-50"> <tr class="hover:bg-gray-50 cursor-pointer" onclick="window.location='{{ route('groups.show', $group) }}'">
<td class="px-4 py-3 font-mono text-gray-600" dir="ltr">{{ $group->code }}</td> <td class="px-4 py-3 font-mono text-gray-600" dir="ltr">{{ $group->code }}</td>
<td class="px-4 py-3"> <td class="px-4 py-3">
<span class="font-medium text-gray-800">{{ $group->name_ar }}</span> <a href="{{ route('groups.show', $group) }}" wire:navigate class="font-medium text-gray-800 hover:text-blue-600">{{ $group->name_ar }}</a>
@if($group->name) @if($group->name)
<p class="text-xs text-gray-500" dir="ltr">{{ $group->name }}</p> <p class="text-xs text-gray-500" dir="ltr">{{ $group->name }}</p>
@endif @endif
......
This diff is collapsed.
...@@ -63,8 +63,8 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 f ...@@ -63,8 +63,8 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 f
</thead> </thead>
<tbody class="divide-y divide-gray-100"> <tbody class="divide-y divide-gray-100">
@forelse($employees as $emp) @forelse($employees as $emp)
<tr class="hover:bg-gray-50"> <tr class="hover:bg-gray-50 cursor-pointer" onclick="window.location='{{ route('employees.show', $emp) }}'">
<td class="px-4 py-3 font-medium text-gray-900">{{ $emp->person?->name_ar ?? '-' }}</td> <td class="px-4 py-3"><a href="{{ route('employees.show', $emp) }}" wire:navigate class="font-medium text-gray-900 hover:text-blue-600">{{ $emp->person?->name_ar ?? '-' }}</a></td>
<td class="px-4 py-3 text-gray-600 font-mono text-xs">{{ $emp->employee_number }}</td> <td class="px-4 py-3 text-gray-600 font-mono text-xs">{{ $emp->employee_number }}</td>
<td class="px-4 py-3 text-gray-600">{{ $emp->position ?? '-' }}</td> <td class="px-4 py-3 text-gray-600">{{ $emp->position ?? '-' }}</td>
<td class="px-4 py-3"> <td class="px-4 py-3">
......
This diff is collapsed.
...@@ -56,9 +56,9 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 f ...@@ -56,9 +56,9 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 f
</thead> </thead>
<tbody class="divide-y divide-gray-100"> <tbody class="divide-y divide-gray-100">
@forelse($trainers as $trainer) @forelse($trainers as $trainer)
<tr class="hover:bg-gray-50"> <tr class="hover:bg-gray-50 cursor-pointer" onclick="window.location='{{ route('trainers.show', $trainer) }}'">
<td class="px-4 py-3"> <td class="px-4 py-3">
<p class="font-medium text-gray-900">{{ $trainer->employee?->person?->name_ar ?? $trainer->person?->name_ar ?? '-' }}</p> <a href="{{ route('trainers.show', $trainer) }}" wire:navigate class="font-medium text-gray-900 hover:text-blue-600">{{ $trainer->employee?->person?->name_ar ?? $trainer->person?->name_ar ?? '-' }}</a>
<p class="text-xs text-gray-500 mt-0.5">{{ $trainer->employee?->branch?->name_ar ?? '' }}</p> <p class="text-xs text-gray-500 mt-0.5">{{ $trainer->employee?->branch?->name_ar ?? '' }}</p>
</td> </td>
<td class="px-4 py-3"> <td class="px-4 py-3">
......
This diff is collapsed.
...@@ -67,12 +67,12 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin ...@@ -67,12 +67,12 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:rin
</thead> </thead>
<tbody class="divide-y divide-gray-100"> <tbody class="divide-y divide-gray-100">
@forelse($products as $product) @forelse($products as $product)
<tr class="hover:bg-gray-50"> <tr class="hover:bg-gray-50 cursor-pointer" onclick="window.location='{{ route('inventory.products.show', $product) }}'">
<td class="px-4 py-3 font-mono text-gray-600 text-xs" dir="ltr"> <td class="px-4 py-3 font-mono text-gray-600 text-xs" dir="ltr">
{{ $product->sku }} {{ $product->sku }}
</td> </td>
<td class="px-4 py-3"> <td class="px-4 py-3">
<span class="font-medium text-gray-800">{{ $product->name_ar }}</span> <a href="{{ route('inventory.products.show', $product) }}" wire:navigate class="font-medium text-gray-800 hover:text-blue-600">{{ $product->name_ar }}</a>
@if($product->name) @if($product->name)
<p class="text-xs text-gray-500" dir="ltr">{{ $product->name }}</p> <p class="text-xs text-gray-500" dir="ltr">{{ $product->name }}</p>
@endif @endif
......
This diff is collapsed.
...@@ -71,12 +71,12 @@ class="w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 f ...@@ -71,12 +71,12 @@ class="w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 f
</thead> </thead>
<tbody class="divide-y divide-gray-100"> <tbody class="divide-y divide-gray-100">
@forelse($participants as $participant) @forelse($participants as $participant)
<tr class="hover:bg-gray-50"> <tr class="hover:bg-gray-50 cursor-pointer" onclick="window.location='{{ route('participants.show', $participant) }}'">
<td class="px-4 py-3 font-mono text-gray-600" dir="ltr"> <td class="px-4 py-3 font-mono text-gray-600" dir="ltr">
{{ $participant->participant_number }} {{ $participant->participant_number }}
</td> </td>
<td class="px-4 py-3"> <td class="px-4 py-3">
<span class="font-medium text-gray-800">{{ $participant->person?->name_ar }}</span> <a href="{{ route('participants.show', $participant) }}" wire:navigate class="font-medium text-gray-800 hover:text-blue-600">{{ $participant->person?->name_ar }}</a>
@if($participant->person?->name) @if($participant->person?->name)
<p class="text-xs text-gray-500" dir="ltr">{{ $participant->person->name }}</p> <p class="text-xs text-gray-500" dir="ltr">{{ $participant->person->name }}</p>
@endif @endif
...@@ -147,7 +147,7 @@ class="w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 f ...@@ -147,7 +147,7 @@ class="w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 f
]; ];
$color = $statusColors[$statusValue] ?? 'gray'; $color = $statusColors[$statusValue] ?? 'gray';
@endphp @endphp
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4"> <div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 hover:border-blue-300 transition">
<div class="flex items-start justify-between gap-3"> <div class="flex items-start justify-between gap-3">
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<p class="font-semibold text-gray-800 truncate">{{ $participant->person?->name_ar }}</p> <p class="font-semibold text-gray-800 truncate">{{ $participant->person?->name_ar }}</p>
......
...@@ -75,10 +75,10 @@ class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 f ...@@ -75,10 +75,10 @@ class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 f
</thead> </thead>
<tbody class="divide-y divide-gray-100"> <tbody class="divide-y divide-gray-100">
@forelse($programs as $program) @forelse($programs as $program)
<tr class="hover:bg-gray-50"> <tr class="hover:bg-gray-50 cursor-pointer" onclick="window.location='{{ route('programs.show', $program) }}'">
<td class="px-4 py-3"> <td class="px-4 py-3">
<div> <div>
<p class="font-medium text-gray-800">{{ $program->name_ar }}</p> <a href="{{ route('programs.show', $program) }}" wire:navigate class="font-medium text-gray-800 hover:text-blue-600">{{ $program->name_ar }}</a>
@if($program->name) @if($program->name)
<p class="text-xs text-gray-400" dir="ltr">{{ $program->name }}</p> <p class="text-xs text-gray-400" dir="ltr">{{ $program->name }}</p>
@endif @endif
......
This diff is collapsed.
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
use App\Livewire\Evaluations\EvaluationShow; use App\Livewire\Evaluations\EvaluationShow;
use App\Livewire\Facilities\FacilityForm; use App\Livewire\Facilities\FacilityForm;
use App\Livewire\Facilities\FacilityList; use App\Livewire\Facilities\FacilityList;
use App\Livewire\Facilities\FacilityShow;
use App\Livewire\Facilities\VisualScheduleBuilder; use App\Livewire\Facilities\VisualScheduleBuilder;
use App\Livewire\Financial\FinancialOverview; use App\Livewire\Financial\FinancialOverview;
use App\Livewire\Auth\Login; use App\Livewire\Auth\Login;
...@@ -33,6 +34,7 @@ ...@@ -33,6 +34,7 @@
use App\Livewire\Enrollments\WaitlistManager; use App\Livewire\Enrollments\WaitlistManager;
use App\Livewire\Groups\GroupForm; use App\Livewire\Groups\GroupForm;
use App\Livewire\Groups\GroupList; use App\Livewire\Groups\GroupList;
use App\Livewire\Groups\GroupShow;
use App\Livewire\Invoices\InvoiceCreate; use App\Livewire\Invoices\InvoiceCreate;
use App\Livewire\Invoices\InvoiceList; use App\Livewire\Invoices\InvoiceList;
use App\Livewire\Invoices\InvoiceShow; use App\Livewire\Invoices\InvoiceShow;
...@@ -56,6 +58,7 @@ ...@@ -56,6 +58,7 @@
use App\Livewire\Pricing\PromotionList; use App\Livewire\Pricing\PromotionList;
use App\Livewire\Programs\ProgramForm; use App\Livewire\Programs\ProgramForm;
use App\Livewire\Programs\ProgramList; use App\Livewire\Programs\ProgramList;
use App\Livewire\Programs\ProgramShow;
use App\Livewire\Settings\ReceiptSettings; use App\Livewire\Settings\ReceiptSettings;
use App\Livewire\Inventory\MovementList; use App\Livewire\Inventory\MovementList;
use App\Livewire\Inventory\ProductForm as InventoryProductForm; use App\Livewire\Inventory\ProductForm as InventoryProductForm;
...@@ -188,6 +191,8 @@ ...@@ -188,6 +191,8 @@
->middleware('permission:programs.list'); ->middleware('permission:programs.list');
Route::get('/programs/create', ProgramForm::class)->name('programs.create') Route::get('/programs/create', ProgramForm::class)->name('programs.create')
->middleware('permission:programs.create'); ->middleware('permission:programs.create');
Route::get('/programs/{program}', ProgramShow::class)->name('programs.show')
->middleware('permission:programs.list');
Route::get('/programs/{program}/edit', ProgramForm::class)->name('programs.edit') Route::get('/programs/{program}/edit', ProgramForm::class)->name('programs.edit')
->middleware('permission:programs.update'); ->middleware('permission:programs.update');
...@@ -198,6 +203,8 @@ ...@@ -198,6 +203,8 @@
->middleware('permission:groups.create'); ->middleware('permission:groups.create');
Route::get('/groups/create', GroupForm::class)->name('groups.create') Route::get('/groups/create', GroupForm::class)->name('groups.create')
->middleware('permission:groups.create'); ->middleware('permission:groups.create');
Route::get('/groups/{group}', GroupShow::class)->name('groups.show')
->middleware('permission:groups.list');
Route::get('/groups/{group}/edit', GroupForm::class)->name('groups.edit') Route::get('/groups/{group}/edit', GroupForm::class)->name('groups.edit')
->middleware('permission:groups.update'); ->middleware('permission:groups.update');
...@@ -246,6 +253,8 @@ ...@@ -246,6 +253,8 @@
->middleware('permission:facilities.create'); ->middleware('permission:facilities.create');
Route::get('/facilities/create', FacilityForm::class)->name('facilities.create') Route::get('/facilities/create', FacilityForm::class)->name('facilities.create')
->middleware('permission:facilities.create'); ->middleware('permission:facilities.create');
Route::get('/facilities/{facility}', FacilityShow::class)->name('facilities.show')
->middleware('permission:facilities.list');
Route::get('/facilities/{facility}/edit', FacilityForm::class)->name('facilities.edit') Route::get('/facilities/{facility}/edit', FacilityForm::class)->name('facilities.edit')
->middleware('permission:facilities.update'); ->middleware('permission:facilities.update');
Route::get('/facilities/{facility}/layouts', \App\Livewire\Facilities\SpaceLayoutManager::class)->name('facilities.layouts') Route::get('/facilities/{facility}/layouts', \App\Livewire\Facilities\SpaceLayoutManager::class)->name('facilities.layouts')
...@@ -274,6 +283,8 @@ ...@@ -274,6 +283,8 @@
->middleware('permission:employees.create'); ->middleware('permission:employees.create');
Route::get('/hr/employees/create', \App\Livewire\HR\EmployeeForm::class)->name('employees.create') Route::get('/hr/employees/create', \App\Livewire\HR\EmployeeForm::class)->name('employees.create')
->middleware('permission:employees.create'); ->middleware('permission:employees.create');
Route::get('/hr/employees/{employee}', \App\Livewire\HR\EmployeeShow::class)->name('employees.show')
->middleware('permission:employees.list');
Route::get('/hr/employees/{employee}/edit', \App\Livewire\HR\EmployeeForm::class)->name('employees.edit') Route::get('/hr/employees/{employee}/edit', \App\Livewire\HR\EmployeeForm::class)->name('employees.edit')
->middleware('permission:employees.update'); ->middleware('permission:employees.update');
...@@ -284,6 +295,8 @@ ...@@ -284,6 +295,8 @@
->middleware('permission:trainers.create'); ->middleware('permission:trainers.create');
Route::get('/hr/trainers/create', \App\Livewire\HR\TrainerForm::class)->name('trainers.create') Route::get('/hr/trainers/create', \App\Livewire\HR\TrainerForm::class)->name('trainers.create')
->middleware('permission:trainers.create'); ->middleware('permission:trainers.create');
Route::get('/hr/trainers/{trainer}', \App\Livewire\HR\TrainerShow::class)->name('trainers.show')
->middleware('permission:trainers.list');
Route::get('/hr/trainers/{trainer}/edit', \App\Livewire\HR\TrainerForm::class)->name('trainers.edit') Route::get('/hr/trainers/{trainer}/edit', \App\Livewire\HR\TrainerForm::class)->name('trainers.edit')
->middleware('permission:trainers.update'); ->middleware('permission:trainers.update');
...@@ -342,6 +355,8 @@ ...@@ -342,6 +355,8 @@
->middleware('permission:inventory.create'); ->middleware('permission:inventory.create');
Route::get('/inventory/products/create', InventoryProductForm::class)->name('inventory.products.create') Route::get('/inventory/products/create', InventoryProductForm::class)->name('inventory.products.create')
->middleware('permission:inventory.create'); ->middleware('permission:inventory.create');
Route::get('/inventory/products/{product}', \App\Livewire\Inventory\ProductShow::class)->name('inventory.products.show')
->middleware('permission:inventory.list');
Route::get('/inventory/products/{product}/edit', InventoryProductForm::class)->name('inventory.products.edit') Route::get('/inventory/products/{product}/edit', InventoryProductForm::class)->name('inventory.products.edit')
->middleware('permission:inventory.update'); ->middleware('permission:inventory.update');
Route::get('/inventory/warehouses', InventoryWarehouseList::class)->name('inventory.warehouses') Route::get('/inventory/warehouses', InventoryWarehouseList::class)->name('inventory.warehouses')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment