- Increased icon sizes in service items, service orders, users, and technician management for better visibility. - Added custom loading indicators with appropriate icons in search fields for vehicles, work orders, and technicians. - Introduced invoice management routes for better organization and access control. - Created a new test for the estimate PDF functionality to ensure proper rendering and data integrity.
569 lines
41 KiB
PHP
569 lines
41 KiB
PHP
<div class="space-y-6">
|
|
<!-- Advanced Header with Stats Dashboard -->
|
|
<div class="bg-gradient-to-r from-accent/10 to-accent/5 rounded-xl p-6 border border-accent/20 dark:border-accent/30">
|
|
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-2">Estimates</h1>
|
|
<p class="text-gray-600 dark:text-gray-400">Manage service estimates, quotes, and customer approvals</p>
|
|
</div>
|
|
<div class="flex space-x-3 mt-4 lg:mt-0">
|
|
<a href="{{ route('estimates.create-standalone') }}" class="inline-flex items-center px-4 py-2 bg-accent hover:bg-accent-content text-accent-foreground font-medium rounded-lg transition-all duration-200 shadow-lg hover:shadow-xl">
|
|
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
New Estimate
|
|
</a>
|
|
@if($availableDiagnoses->count() > 0)
|
|
<div class="relative" x-data="{ open: false }">
|
|
<button @click="open = !open" class="inline-flex items-center px-4 py-2 border border-accent text-accent bg-white dark:bg-gray-800 hover:bg-accent/10 dark:hover:bg-accent/20 font-medium rounded-lg transition-all duration-200">
|
|
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
From Diagnosis
|
|
<svg class="w-6 h-6 ml-2" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</button>
|
|
<div x-show="open" @click.away="open = false" x-transition class="absolute top-full left-0 mt-2 w-64 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 z-10">
|
|
<div class="p-3">
|
|
<p class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">Create estimate from diagnosis:</p>
|
|
<div class="space-y-2 max-h-40 overflow-y-auto">
|
|
@foreach($availableDiagnoses as $diagnosis)
|
|
<a href="{{ route('estimates.create', $diagnosis) }}" class="block p-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
{{ $diagnosis->jobCard?->customer?->name ?? 'Unknown Customer' }}
|
|
</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400">
|
|
{{ $diagnosis->jobCard?->vehicle?->year }} {{ $diagnosis->jobCard?->vehicle?->make }} {{ $diagnosis->jobCard?->vehicle?->model }}
|
|
</div>
|
|
</a>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
<button wire:click="toggleBulkMode" class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
{{ $bulkMode ? 'Exit Bulk Mode' : 'Bulk Actions' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Dashboard -->
|
|
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-4">
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700 hover:shadow-md transition-shadow">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
|
|
<svg class="w-6 h-6 text-blue-600 dark:text-blue-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Total</p>
|
|
<p class="text-lg font-bold text-gray-900 dark:text-gray-100">{{ number_format($stats['total']) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700 hover:shadow-md transition-shadow">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-gray-100 dark:bg-gray-700 rounded-lg">
|
|
<svg class="w-6 h-6 text-gray-600 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M4 4a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2H4zm0 2h12v8H4V6z"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Draft</p>
|
|
<p class="text-lg font-bold text-gray-700">{{ number_format($stats['draft']) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700 hover:shadow-md transition-shadow">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-blue-100 dark:bg-blue-900/50 rounded-lg">
|
|
<svg class="w-6 h-6 text-blue-600 dark:text-blue-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"></path>
|
|
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Sent</p>
|
|
<p class="text-lg font-bold text-blue-600 dark:text-blue-400">{{ number_format($stats['sent']) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700 hover:shadow-md transition-shadow">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-green-100 dark:bg-green-900/50 rounded-lg">
|
|
<svg class="w-6 h-6 text-green-600 dark:text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Approved</p>
|
|
<p class="text-lg font-bold text-green-600 dark:text-green-400">{{ number_format($stats['approved']) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700 hover:shadow-md transition-shadow">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-yellow-100 dark:bg-yellow-900/50 rounded-lg">
|
|
<svg class="w-6 h-6 text-yellow-600 dark:text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Pending</p>
|
|
<p class="text-lg font-bold text-yellow-600 dark:text-yellow-400">{{ number_format($stats['pending']) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700 hover:shadow-md transition-shadow">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-red-100 dark:bg-red-900/50 rounded-lg">
|
|
<svg class="w-6 h-6 text-red-600 dark:text-red-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Expired</p>
|
|
<p class="text-lg font-bold text-red-600 dark:text-red-400">{{ number_format($stats['expired']) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700 hover:shadow-md transition-shadow">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-green-100 dark:bg-green-900/50 rounded-lg">
|
|
<svg class="w-6 h-6 text-green-600 dark:text-green-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M8.433 7.418c.155-.103.346-.196.567-.267v1.698a2.305 2.305 0 01-.567-.267C8.07 8.34 8 8.114 8 8c0-.114.07-.34.433-.582z"></path>
|
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Value</p>
|
|
<p class="text-lg font-bold text-green-600 dark:text-green-400">${{ number_format($stats['total_value'], 0) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700 hover:shadow-md transition-shadow">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-purple-100 dark:bg-purple-900/50 rounded-lg">
|
|
<svg class="w-6 h-6 text-purple-600 dark:text-purple-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Avg Value</p>
|
|
<p class="text-lg font-bold text-purple-600 dark:text-purple-400">${{ number_format($stats['avg_value'], 0) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Advanced Filters & Search -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Filters & Search</h3>
|
|
@if($search || $statusFilter || $approvalStatusFilter || $customerFilter || $dateFrom || $dateTo)
|
|
<div class="flex items-center space-x-2">
|
|
<button wire:click="clearFilters" class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
<svg class="w-6 h-6 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
Clear Filters
|
|
</button>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<!-- Basic Filters Row -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Search</flux:label>
|
|
<flux:input wire:model.live.debounce.300ms="search" placeholder="Estimate #, customer name, vehicle..." />
|
|
</flux:field>
|
|
</div>
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Status</flux:label>
|
|
<flux:select wire:model.live="statusFilter" placeholder="All Statuses">
|
|
<option value="">All Statuses</option>
|
|
<option value="draft">Draft</option>
|
|
<option value="sent">Sent</option>
|
|
<option value="approved">Approved</option>
|
|
<option value="rejected">Rejected</option>
|
|
<option value="expired">Expired</option>
|
|
<option value="pending_approval">Pending Approval</option>
|
|
</flux:select>
|
|
</flux:field>
|
|
</div>
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Approval Status</flux:label>
|
|
<flux:select wire:model.live="approvalStatusFilter" placeholder="All Approvals">
|
|
<option value="">All Approvals</option>
|
|
<option value="pending">Pending</option>
|
|
<option value="approved">Approved</option>
|
|
<option value="rejected">Rejected</option>
|
|
</flux:select>
|
|
</flux:field>
|
|
</div>
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Customer</flux:label>
|
|
<flux:select wire:model.live="customerFilter" placeholder="All Customers">
|
|
<option value="">All Customers</option>
|
|
@foreach($customers as $customer)
|
|
<option value="{{ $customer->id }}">{{ $customer->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
</flux:field>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Advanced Filters (Collapsible) -->
|
|
@if($showAdvancedFilters)
|
|
<div class="border-t border-gray-200 pt-4">
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Date From</flux:label>
|
|
<flux:input wire:model.live="dateFrom" type="date" />
|
|
</flux:field>
|
|
</div>
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Date To</flux:label>
|
|
<flux:input wire:model.live="dateTo" type="date" />
|
|
</flux:field>
|
|
</div>
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Min Amount ($)</flux:label>
|
|
<flux:input wire:model.live="totalAmountMin" type="number" step="0.01" placeholder="0.00" />
|
|
</flux:field>
|
|
</div>
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Max Amount ($)</flux:label>
|
|
<flux:input wire:model.live="totalAmountMax" type="number" step="0.01" placeholder="999999.99" />
|
|
</flux:field>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Validity Status</flux:label>
|
|
<flux:select wire:model.live="validityFilter" placeholder="All Validity">
|
|
<option value="">All Validity</option>
|
|
<option value="valid">Valid</option>
|
|
<option value="expiring_soon">Expiring Soon (7 days)</option>
|
|
<option value="expired">Expired</option>
|
|
</flux:select>
|
|
</flux:field>
|
|
</div>
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Branch</flux:label>
|
|
<flux:select wire:model.live="branchFilter" placeholder="All Branches">
|
|
<option value="">All Branches</option>
|
|
@foreach($branches as $branch)
|
|
<option value="{{ $branch->code }}">{{ $branch->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
</flux:field>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<!-- Bulk Actions Bar (Show when bulk mode is active and items selected) -->
|
|
@if($bulkMode && !empty($selectedEstimates))
|
|
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center space-x-4">
|
|
<span class="text-sm font-medium text-blue-900">{{ count($selectedEstimates) }} estimate(s) selected</span>
|
|
</div>
|
|
<div class="flex space-x-2">
|
|
<button wire:click="bulkAction('mark_sent')" class="inline-flex items-center px-3 py-2 border border-blue-300 dark:border-blue-600 rounded-lg text-sm font-medium text-blue-700 dark:text-blue-300 bg-white dark:bg-gray-800 hover:bg-blue-50 dark:hover:bg-blue-900/50 transition-colors">
|
|
<svg class="w-6 h-6 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"></path>
|
|
</svg>
|
|
Mark as Sent
|
|
</button>
|
|
<button wire:click="bulkAction('export')" class="inline-flex items-center px-3 py-2 border border-green-300 dark:border-green-600 rounded-lg text-sm font-medium text-green-700 dark:text-green-300 bg-white dark:bg-gray-800 hover:bg-green-50 dark:hover:bg-green-900/50 transition-colors">
|
|
<svg class="w-6 h-6 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
|
</svg>
|
|
Export
|
|
</button>
|
|
<button wire:click="bulkAction('delete')" class="inline-flex items-center px-3 py-2 border border-red-300 dark:border-red-600 rounded-lg text-sm font-medium text-red-700 dark:text-red-300 bg-white dark:bg-gray-800 hover:bg-red-50 dark:hover:bg-red-900/50 transition-colors">
|
|
<svg class="w-6 h-6 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
|
</svg>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Main Estimates Table -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
<thead class="bg-gradient-to-r from-accent/10 to-accent/5 dark:from-accent/20 dark:to-accent/10">
|
|
<tr>
|
|
@if($bulkMode)
|
|
<th class="px-6 py-3 text-left">
|
|
<flux:checkbox wire:model.live="selectAll" />
|
|
</th>
|
|
@endif
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-900 dark:text-gray-100 uppercase tracking-wider cursor-pointer hover:bg-accent/20 dark:hover:bg-accent/30 transition-colors rounded-tl-lg" wire:click="sortBy('estimate_number')">
|
|
<div class="flex items-center space-x-1">
|
|
<span>Estimate #</span>
|
|
@if($sortBy === 'estimate_number')
|
|
<svg class="w-6 h-6 text-accent" fill="currentColor" viewBox="0 0 20 20">
|
|
@if($sortDirection === 'asc')
|
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
@else
|
|
<path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd"></path>
|
|
@endif
|
|
</svg>
|
|
@endif
|
|
</div>
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-900 dark:text-gray-100 uppercase tracking-wider cursor-pointer hover:bg-accent/20 dark:hover:bg-accent/30 transition-colors" wire:click="sortBy('created_at')">
|
|
<div class="flex items-center space-x-1">
|
|
<span>Date</span>
|
|
@if($sortBy === 'created_at')
|
|
<svg class="w-6 h-6 text-accent" fill="currentColor" viewBox="0 0 20 20">
|
|
@if($sortDirection === 'asc')
|
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
@else
|
|
<path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd"></path>
|
|
@endif
|
|
</svg>
|
|
@endif
|
|
</div>
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-900 dark:text-gray-100 uppercase tracking-wider">Customer</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-900 dark:text-gray-100 uppercase tracking-wider">Vehicle</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-900 dark:text-gray-100 uppercase tracking-wider">Status</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-900 dark:text-gray-100 uppercase tracking-wider">Approval</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-900 dark:text-gray-100 uppercase tracking-wider cursor-pointer hover:bg-accent/20 dark:hover:bg-accent/30 transition-colors" wire:click="sortBy('total_amount')">
|
|
<div class="flex items-center space-x-1">
|
|
<span>Total</span>
|
|
@if($sortBy === 'total_amount')
|
|
<svg class="w-6 h-6 text-accent" fill="currentColor" viewBox="0 0 20 20">
|
|
@if($sortDirection === 'asc')
|
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
@else
|
|
<path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd"></path>
|
|
@endif
|
|
</svg>
|
|
@endif
|
|
</div>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
@forelse($estimates as $estimate)
|
|
<tr class="cursor-pointer transition-colors {{ $selectedRow === $estimate->id ? 'bg-accent/20 border-l-4 border-accent' : 'hover:bg-accent/10 dark:hover:bg-accent/20' }}"
|
|
wire:key="estimate-{{ $estimate->id }}"
|
|
wire:click="selectRow({{ $estimate->id }})">
|
|
@if($bulkMode)
|
|
<td class="px-6 py-4 whitespace-nowrap" onclick="event.stopPropagation()">
|
|
<flux:checkbox wire:model.live="selectedEstimates" value="{{ $estimate->id }}" />
|
|
</td>
|
|
@endif
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex flex-col">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">{{ $estimate->estimate_number }}</div>
|
|
|
|
<!-- Inline Actions - Only show when this row is selected -->
|
|
@if($selectedRow === $estimate->id)
|
|
<div class="flex items-center space-x-1 mt-2">
|
|
@php
|
|
$actions = [];
|
|
|
|
// Edit (first) - temporarily bypass policy for super admin
|
|
if(auth()->user()->hasRole('super_admin') || auth()->user()->can('update', $estimate)) {
|
|
$actions[] = '<a href="' . route('estimates.edit', $estimate) . '" class="text-xs text-amber-600 hover:text-amber-800 dark:text-amber-400 dark:hover:text-amber-300 font-medium" onclick="event.stopPropagation()">edit</a>';
|
|
}
|
|
|
|
// View (second) - temporarily bypass policy for super admin
|
|
if(auth()->user()->hasRole('super_admin') || auth()->user()->can('view', $estimate)) {
|
|
$actions[] = '<a href="' . route('estimates.show', $estimate) . '" class="text-xs text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 font-medium" onclick="event.stopPropagation()">view</a>';
|
|
}
|
|
|
|
// Delete (third) - temporarily bypass policy for super admin
|
|
if(auth()->user()->hasRole('super_admin') || auth()->user()->can('delete', $estimate)) {
|
|
$actions[] = '<button onclick="event.stopPropagation(); confirmDeleteEstimate(' . $estimate->id . ')" class="text-xs text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300 font-medium">delete</button>';
|
|
}
|
|
|
|
// Send/Resend (fourth)
|
|
if($estimate->status === 'draft') {
|
|
$actions[] = '<button wire:click="sendEstimate(' . $estimate->id . ')" class="text-xs text-green-600 hover:text-green-800 dark:text-green-400 dark:hover:text-green-300 font-medium" onclick="event.stopPropagation()">send</button>';
|
|
} elseif(in_array($estimate->status, ['sent', 'approved', 'rejected'])) {
|
|
$actions[] = '<button wire:click="resendEstimate(' . $estimate->id . ')" class="text-xs text-green-600 hover:text-green-800 dark:text-green-400 dark:hover:text-green-300 font-medium" onclick="event.stopPropagation()">resend</button>';
|
|
}
|
|
@endphp
|
|
|
|
{!! implode(' <span class="text-gray-400 text-xs">|</span> ', $actions) !!}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm text-gray-900 dark:text-gray-100">{{ $estimate->created_at->format('M j, Y') }}</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400">{{ $estimate->created_at->format('g:i A') }}</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="flex items-center">
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
{{ $estimate->customer_id ? $estimate->customer?->name : $estimate->jobCard?->customer?->name ?? 'Unknown Customer' }}
|
|
</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400">
|
|
{{ $estimate->customer_id ? $estimate->customer?->email : $estimate->jobCard?->customer?->email ?? 'No email' }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm text-gray-900 dark:text-gray-100">
|
|
@if($estimate->vehicle_id)
|
|
{{ $estimate->vehicle?->year }} {{ $estimate->vehicle?->make }} {{ $estimate->vehicle?->model }}
|
|
@else
|
|
{{ $estimate->jobCard?->vehicle?->year }} {{ $estimate->jobCard?->vehicle?->make }} {{ $estimate->jobCard?->vehicle?->model }}
|
|
@endif
|
|
</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400">
|
|
{{ $estimate->vehicle_id ? $estimate->vehicle?->license_plate : $estimate->jobCard?->vehicle?->license_plate }}
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
@php
|
|
$statusColors = [
|
|
'draft' => 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300',
|
|
'sent' => 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300',
|
|
'viewed' => 'bg-purple-100 text-purple-800 dark:bg-purple-900/50 dark:text-purple-300',
|
|
'approved' => 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300',
|
|
'rejected' => 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300',
|
|
'expired' => 'bg-accent/20 text-accent dark:bg-accent/30 dark:text-accent-foreground',
|
|
];
|
|
@endphp
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $statusColors[$estimate->status] ?? 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' }}">
|
|
{{ ucfirst($estimate->status) }}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
@php
|
|
$approvalColors = [
|
|
'pending' => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300',
|
|
'approved' => 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-300',
|
|
'rejected' => 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300',
|
|
];
|
|
@endphp
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $approvalColors[$estimate->customer_approval_status] ?? 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' }}">
|
|
{{ ucfirst(str_replace('_', ' ', $estimate->customer_approval_status)) }}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
${{ number_format($estimate->total_amount, 2) }}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="{{ $bulkMode ? '9' : '8' }}" class="px-6 py-12 text-center">
|
|
<div class="flex flex-col items-center justify-center space-y-3">
|
|
<svg class="w-12 h-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
|
</svg>
|
|
<div class="text-gray-500">
|
|
<p class="text-lg font-medium">No estimates found</p>
|
|
<p class="text-sm">Get started by creating your first estimate</p>
|
|
</div>
|
|
@if($availableDiagnoses->count() > 0)
|
|
<div class="relative" x-data="{ open: false }">
|
|
<button @click="open = !open" class="inline-flex items-center px-4 py-2 bg-accent hover:bg-accent-content text-accent-foreground font-medium rounded-lg transition-all duration-200">
|
|
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
Create New Estimate
|
|
<svg class="w-6 h-6 ml-2" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</button>
|
|
<div x-show="open" @click.away="open = false" x-transition class="absolute bottom-full left-0 mb-2 w-64 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 z-10">
|
|
<div class="p-3">
|
|
<p class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">Select a diagnosis to create estimate for:</p>
|
|
<div class="space-y-2 max-h-40 overflow-y-auto">
|
|
@foreach($availableDiagnoses as $diagnosis)
|
|
<a href="{{ route('estimates.create', $diagnosis) }}" class="block p-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
{{ $diagnosis->jobCard?->customer?->name ?? 'Unknown Customer' }}
|
|
</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400">
|
|
{{ $diagnosis->jobCard?->vehicle?->year }} {{ $diagnosis->jobCard?->vehicle?->make }} {{ $diagnosis->jobCard?->vehicle?->model }}
|
|
</div>
|
|
</a>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@else
|
|
<div class="text-center">
|
|
<p class="text-sm text-gray-500 dark:text-gray-400 mb-3">No diagnoses available for estimate creation.</p>
|
|
<a href="{{ route('job-cards.index') }}" class="inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-all duration-200">
|
|
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h8a2 2 0 012 2v12a1 1 0 110 2h-3a1 1 0 01-1-1v-2a1 1 0 00-1-1H9a1 1 0 00-1 1v2a1 1 0 01-1 1H4a1 1 0 110-2V4z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
Go to Job Cards
|
|
</a>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<div class="mt-6 flex justify-between items-center">
|
|
<div class="text-sm text-gray-700 dark:text-gray-300">
|
|
Showing {{ $estimates->firstItem() ?? 0 }} to {{ $estimates->lastItem() ?? 0 }} of {{ $estimates->total() }} estimates
|
|
</div>
|
|
<div>
|
|
{{ $estimates->links() }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function confirmDeleteEstimate(estimateId) {
|
|
if (confirm('Are you sure you want to delete this estimate? This action cannot be undone.')) {
|
|
@this.call('confirmDelete', estimateId);
|
|
}
|
|
}
|
|
</script>
|