0, 'received' => 0, 'in_progress' => 0, 'pending_approval' => 0, 'completed_today' => 0, 'delivered_today' => 0, 'overdue' => 0, ]; protected $queryString = [ 'search' => ['except' => ''], 'statusFilter' => ['except' => ''], 'branchFilter' => ['except' => ''], 'priorityFilter' => ['except' => ''], 'serviceAdvisorFilter' => ['except' => ''], 'dateRange' => ['except' => ''], 'sortBy' => ['except' => 'created_at'], 'sortDirection' => ['except' => 'desc'], ]; public function boot() { // Ensure properties are properly initialized $this->selectedJobCards = $this->selectedJobCards ?? []; $this->statistics = $this->statistics ?? [ 'total' => 0, 'received' => 0, 'in_progress' => 0, 'pending_approval' => 0, 'completed_today' => 0, 'delivered_today' => 0, 'overdue' => 0, ]; } public function mount() { $this->boot(); // Ensure properties are initialized $this->authorize('viewAny', JobCard::class); // Add debug information to debugbar if (app()->bound('debugbar')) { debugbar()->info('JobCard Index component mounted'); debugbar()->addMessage('User: ' . auth()->user()->name, 'user'); debugbar()->addMessage('User permissions checked for JobCard access', 'auth'); } $this->loadStatistics(); } public function updatingSearch() { $this->resetPage(); $this->selectedJobCards = []; $this->selectAll = false; $this->loadStatistics(); } public function updatingStatusFilter() { $this->resetPage(); $this->selectedJobCards = []; $this->selectAll = false; $this->loadStatistics(); } public function updatingBranchFilter() { $this->resetPage(); $this->selectedJobCards = []; $this->selectAll = false; $this->loadStatistics(); } public function updatingPriorityFilter() { $this->resetPage(); $this->selectedJobCards = []; $this->selectAll = false; $this->loadStatistics(); } public function updatingServiceAdvisorFilter() { $this->resetPage(); $this->selectedJobCards = []; $this->selectAll = false; $this->loadStatistics(); } public function updatingDateRange() { $this->resetPage(); $this->selectedJobCards = []; $this->selectAll = false; $this->loadStatistics(); } public function sortBy($field) { if ($this->sortBy === $field) { $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; } else { $this->sortBy = $field; $this->sortDirection = 'asc'; } $this->resetPage(); } public function refreshData() { $this->loadStatistics(); $this->selectedJobCards = []; $this->selectAll = false; session()->flash('success', 'Data refreshed successfully.'); } public function clearFilters() { $this->search = ''; $this->statusFilter = ''; $this->branchFilter = ''; $this->priorityFilter = ''; $this->serviceAdvisorFilter = ''; $this->dateRange = ''; $this->selectedJobCards = []; $this->selectAll = false; $this->resetPage(); $this->loadStatistics(); session()->flash('success', 'Filters cleared successfully.'); } /** * Get workflow progress percentage for a job card */ public function getWorkflowProgress($status) { $steps = [ JobCard::STATUS_RECEIVED => 1, JobCard::STATUS_INSPECTED => 2, JobCard::STATUS_ASSIGNED_FOR_DIAGNOSIS => 3, JobCard::STATUS_IN_DIAGNOSIS => 4, JobCard::STATUS_ESTIMATE_SENT => 5, JobCard::STATUS_APPROVED => 6, JobCard::STATUS_PARTS_PROCUREMENT => 7, JobCard::STATUS_IN_PROGRESS => 8, JobCard::STATUS_QUALITY_REVIEW_REQUIRED => 9, JobCard::STATUS_COMPLETED => 10, JobCard::STATUS_DELIVERED => 11, ]; $currentStep = $steps[$status] ?? 1; return round(($currentStep / 11) * 100); } public function loadStatistics() { try { if (app()->bound('debugbar')) { debugbar()->startMeasure('statistics', 'Loading JobCard Statistics'); } $user = auth()->user(); $query = JobCard::query(); // Apply branch filtering based on user permissions if (!$user->hasPermission('job-cards.view-all')) { if ($user->hasPermission('job-cards.view-own')) { $query->where('service_advisor_id', $user->id); } elseif ($user->hasPermission('job-cards.view')) { $query->where('branch_code', $user->branch_code); } } $this->statistics = [ 'total' => $query->count(), 'received' => (clone $query)->where('status', JobCard::STATUS_RECEIVED)->count(), 'in_progress' => (clone $query)->whereIn('status', [ JobCard::STATUS_IN_DIAGNOSIS, JobCard::STATUS_IN_PROGRESS, JobCard::STATUS_PARTS_PROCUREMENT ])->count(), 'pending_approval' => (clone $query)->where('status', JobCard::STATUS_ESTIMATE_SENT)->count(), 'completed_today' => (clone $query)->where('status', JobCard::STATUS_COMPLETED) ->whereDate('completion_datetime', today())->count(), 'delivered_today' => (clone $query)->where('status', JobCard::STATUS_DELIVERED) ->whereDate('completion_datetime', today())->count(), 'overdue' => (clone $query)->where('expected_completion_date', '<', now()) ->whereNotIn('status', [JobCard::STATUS_COMPLETED, JobCard::STATUS_DELIVERED]) ->count(), ]; if (app()->bound('debugbar')) { debugbar()->stopMeasure('statistics'); debugbar()->addMessage('Statistics loaded: ' . json_encode($this->statistics), 'statistics'); } } catch (\Exception $e) { // Fallback statistics if there's an error $this->statistics = [ 'total' => 0, 'received' => 0, 'in_progress' => 0, 'pending_approval' => 0, 'completed_today' => 0, 'delivered_today' => 0, 'overdue' => 0, ]; if (app()->bound('debugbar')) { debugbar()->error('Error loading JobCard statistics: ' . $e->getMessage()); } logger()->error('Error loading JobCard statistics: ' . $e->getMessage()); } } public function updatedSelectAll() { if ($this->selectAll) { try { $this->selectedJobCards = $this->getJobCards()->pluck('id')->toArray(); } catch (\Exception $e) { $this->selectedJobCards = []; $this->selectAll = false; session()->flash('error', 'Unable to select all job cards. Please try again.'); } } else { $this->selectedJobCards = []; } } public function processBulkAction() { if (empty($this->selectedJobCards) || empty($this->bulkAction)) { session()->flash('error', 'Please select job cards and an action.'); return; } $successCount = 0; $errorCount = 0; foreach ($this->selectedJobCards as $jobCardId) { try { $jobCard = JobCard::find($jobCardId); if (!$jobCard) continue; switch ($this->bulkAction) { case 'export_csv': return $this->exportSelected(); break; } } catch (\Exception $e) { $errorCount++; } } $this->selectedJobCards = []; $this->selectAll = false; $this->bulkAction = ''; $this->loadStatistics(); // Refresh statistics after bulk operations if ($successCount > 0) { session()->flash('success', "{$successCount} job cards processed successfully."); } if ($errorCount > 0) { session()->flash('error', "{$errorCount} job cards failed to process."); } } public function exportSelected() { if (empty($this->selectedJobCards)) { session()->flash('error', 'Please select job cards to export.'); return; } $jobCards = JobCard::with(['customer', 'vehicle', 'serviceAdvisor']) ->whereIn('id', $this->selectedJobCards) ->get(); $csv = "Job Card Number,Customer,Vehicle,Service Advisor,Status,Priority,Created Date,Expected Completion\n"; foreach ($jobCards as $jobCard) { $csv .= sprintf( "%s,%s,%s,%s,%s,%s,%s,%s\n", $jobCard->job_card_number, $jobCard->customer->full_name ?? '', $jobCard->vehicle->display_name ?? '', $jobCard->serviceAdvisor->name ?? '', $jobCard->status, $jobCard->priority, $jobCard->created_at->format('Y-m-d'), $jobCard->expected_completion_date ? $jobCard->expected_completion_date->format('Y-m-d') : '' ); } return response()->streamDownload(function () use ($csv) { echo $csv; }, 'job-cards-' . date('Y-m-d') . '.csv', [ 'Content-Type' => 'text/csv', ]); } protected function getJobCards() { try { $user = auth()->user(); $query = JobCard::query() ->with(['customer', 'vehicle', 'serviceAdvisor']); // Apply permission-based filtering if (!$user->hasPermission('job-cards.view-all')) { if ($user->hasPermission('job-cards.view-own')) { $query->where('service_advisor_id', $user->id); } elseif ($user->hasPermission('job-cards.view')) { $query->where('branch_code', $user->branch_code); } } // Apply filters if ($this->search) { $query->where(function ($q) { $q->where('job_card_number', 'like', '%' . $this->search . '%') ->orWhereHas('customer', function ($customerQuery) { $customerQuery->where('first_name', 'like', '%' . $this->search . '%') ->orWhere('last_name', 'like', '%' . $this->search . '%') ->orWhere('email', 'like', '%' . $this->search . '%'); }) ->orWhereHas('vehicle', function ($vehicleQuery) { $vehicleQuery->where('license_plate', 'like', '%' . $this->search . '%') ->orWhere('vin', 'like', '%' . $this->search . '%'); }); }); } if ($this->statusFilter) { $query->where('status', $this->statusFilter); } if ($this->branchFilter) { $query->where('branch_code', $this->branchFilter); } if ($this->priorityFilter) { $query->where('priority', $this->priorityFilter); } if ($this->serviceAdvisorFilter) { $query->where('service_advisor_id', $this->serviceAdvisorFilter); } if ($this->dateRange) { switch ($this->dateRange) { case 'today': $query->whereDate('created_at', today()); break; case 'week': $query->whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()]); break; case 'month': $query->whereMonth('created_at', now()->month) ->whereYear('created_at', now()->year); break; case 'overdue': $query->where('expected_completion_date', '<', now()) ->whereNotIn('status', [JobCard::STATUS_COMPLETED, JobCard::STATUS_DELIVERED]); break; } } return $query->orderBy($this->sortBy, $this->sortDirection); } catch (\Exception $e) { logger()->error('Error in getJobCards query: ' . $e->getMessage()); // Return empty query as fallback return JobCard::query()->whereRaw('1 = 0'); // Returns empty result set } } public function render() { try { // Ensure statistics are always fresh and available if (empty($this->statistics) || !isset($this->statistics['total'])) { $this->loadStatistics(); } $jobCards = $this->getJobCards()->paginate(20); $statusOptions = JobCard::getStatusOptions(); $priorityOptions = [ 'low' => 'Low', 'medium' => 'Medium', 'high' => 'High', 'urgent' => 'Urgent', ]; $branchOptions = Branch::active() ->orderBy('name') ->pluck('name', 'code') ->toArray(); $serviceAdvisorOptions = User::whereHas('roles', function ($query) { $query->whereIn('name', ['service_advisor', 'service_supervisor']); }) ->where('status', 'active') ->orderBy('name') ->pluck('name', 'id') ->toArray(); $dateRangeOptions = [ 'today' => 'Today', 'week' => 'This Week', 'month' => 'This Month', 'overdue' => 'Overdue', ]; return view('livewire.job-cards.index', compact( 'jobCards', 'statusOptions', 'priorityOptions', 'branchOptions', 'serviceAdvisorOptions', 'dateRangeOptions' ))->with([ 'statistics' => $this->statistics, 'selectedJobCards' => $this->selectedJobCards ?? [], 'selectAll' => $this->selectAll ?? false, 'bulkAction' => $this->bulkAction ?? '', 'search' => $this->search ?? '', 'statusFilter' => $this->statusFilter ?? '', 'branchFilter' => $this->branchFilter ?? '', 'priorityFilter' => $this->priorityFilter ?? '', 'serviceAdvisorFilter' => $this->serviceAdvisorFilter ?? '', 'dateRange' => $this->dateRange ?? '', 'sortBy' => $this->sortBy ?? 'created_at', 'sortDirection' => $this->sortDirection ?? 'desc' ]); } catch (\Exception $e) { logger()->error('Error rendering JobCard Index: ' . $e->getMessage()); // Provide fallback data $jobCards = collect()->paginate(20); $statusOptions = []; $priorityOptions = []; $branchOptions = []; $serviceAdvisorOptions = []; $dateRangeOptions = []; $statistics = $this->statistics ?? []; session()->flash('error', 'There was an error loading the job cards. Please try again.'); return view('livewire.job-cards.index', compact( 'jobCards', 'statusOptions', 'priorityOptions', 'branchOptions', 'serviceAdvisorOptions', 'dateRangeOptions' ))->with([ 'statistics' => $statistics, 'selectedJobCards' => $this->selectedJobCards ?? [], 'selectAll' => $this->selectAll ?? false, 'bulkAction' => $this->bulkAction ?? '', 'search' => $this->search ?? '', 'statusFilter' => $this->statusFilter ?? '', 'branchFilter' => $this->branchFilter ?? '', 'priorityFilter' => $this->priorityFilter ?? '', 'serviceAdvisorFilter' => $this->serviceAdvisorFilter ?? '', 'dateRange' => $this->dateRange ?? '' ]); } } /** * Handle the component invocation for route compatibility */ public function __invoke() { return $this->render(); } }