- 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.
300 lines
16 KiB
PHP
300 lines
16 KiB
PHP
<div>
|
|
<!-- Header -->
|
|
<div class="mb-8">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Invoices</h1>
|
|
<p class="text-gray-600 dark:text-gray-400">Manage customer invoices and billing</p>
|
|
</div>
|
|
<div class="flex space-x-4">
|
|
<flux:button variant="outline" href="{{ route('invoices.create') }}">
|
|
<flux:icon.plus class="w-4 h-4 mr-2" />
|
|
New Invoice
|
|
</flux:button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg shadow p-6 mb-6">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Search</flux:label>
|
|
<flux:input wire:model.live.debounce.300ms="search" placeholder="Invoice number, customer name..." />
|
|
</flux:field>
|
|
</div>
|
|
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Status</flux:label>
|
|
<flux:select wire:model.live="filterStatus" placeholder="All Statuses">
|
|
<option value="">All Statuses</option>
|
|
@foreach($statusOptions as $value => $label)
|
|
<option value="{{ $value }}">{{ $label }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
</flux:field>
|
|
</div>
|
|
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Branch</flux:label>
|
|
<flux:select wire:model.live="filterBranch" placeholder="All Branches">
|
|
<option value="">All Branches</option>
|
|
@foreach($branches as $branch)
|
|
<option value="{{ $branch->id }}">{{ $branch->name }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
</flux:field>
|
|
</div>
|
|
|
|
<div class="flex items-end">
|
|
<flux:button variant="outline" wire:click="clearFilters">
|
|
Clear Filters
|
|
</flux:button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Date From</flux:label>
|
|
<flux:input type="date" wire:model.live="filterDateFrom" />
|
|
</flux:field>
|
|
</div>
|
|
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Date To</flux:label>
|
|
<flux:input type="date" wire:model.live="filterDateTo" />
|
|
</flux:field>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Summary Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg shadow p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-blue-100 dark:bg-blue-900 rounded-lg">
|
|
<flux:icon.document-text class="w-6 h-6 text-blue-600 dark:text-blue-400" />
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Invoices</p>
|
|
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ number_format($invoices->total()) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg shadow p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-green-100 dark:bg-green-900 rounded-lg">
|
|
<flux:icon.check-circle class="w-6 h-6 text-green-600 dark:text-green-400" />
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Paid</p>
|
|
<p class="text-2xl font-bold text-green-600 dark:text-green-400">
|
|
{{ $invoices->where('status', 'paid')->count() }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg shadow p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-yellow-100 dark:bg-yellow-900 rounded-lg">
|
|
<flux:icon.clock class="w-6 h-6 text-yellow-600 dark:text-yellow-400" />
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Pending</p>
|
|
<p class="text-2xl font-bold text-yellow-600 dark:text-yellow-400">
|
|
{{ $invoices->whereIn('status', ['draft', 'sent'])->count() }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg shadow p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-2 bg-red-100 dark:bg-red-900 rounded-lg">
|
|
<flux:icon.exclamation-triangle class="w-6 h-6 text-red-600 dark:text-red-400" />
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Overdue</p>
|
|
<p class="text-2xl font-bold text-red-600 dark:text-red-400">
|
|
{{ $invoices->where('status', 'overdue')->count() }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Invoices Table -->
|
|
<div class="bg-white dark:bg-zinc-800 rounded-lg shadow overflow-hidden">
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-zinc-700">
|
|
<thead class="bg-gray-50 dark:bg-zinc-900">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer" wire:click="sortBy('invoice_number')">
|
|
<div class="flex items-center space-x-1">
|
|
<span>Invoice #</span>
|
|
@if($sortField === 'invoice_number')
|
|
@if($sortDirection === 'asc')
|
|
<flux:icon.chevron-up class="w-4 h-4" />
|
|
@else
|
|
<flux:icon.chevron-down class="w-4 h-4" />
|
|
@endif
|
|
@endif
|
|
</div>
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Customer</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer" wire:click="sortBy('invoice_date')">
|
|
<div class="flex items-center space-x-1">
|
|
<span>Date</span>
|
|
@if($sortField === 'invoice_date')
|
|
@if($sortDirection === 'asc')
|
|
<flux:icon.chevron-up class="w-4 h-4" />
|
|
@else
|
|
<flux:icon.chevron-down class="w-4 h-4" />
|
|
@endif
|
|
@endif
|
|
</div>
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer" wire:click="sortBy('due_date')">
|
|
<div class="flex items-center space-x-1">
|
|
<span>Due Date</span>
|
|
@if($sortField === 'due_date')
|
|
@if($sortDirection === 'asc')
|
|
<flux:icon.chevron-up class="w-4 h-4" />
|
|
@else
|
|
<flux:icon.chevron-down class="w-4 h-4" />
|
|
@endif
|
|
@endif
|
|
</div>
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider cursor-pointer" wire:click="sortBy('total_amount')">
|
|
<div class="flex items-center space-x-1">
|
|
<span>Amount</span>
|
|
@if($sortField === 'total_amount')
|
|
@if($sortDirection === 'asc')
|
|
<flux:icon.chevron-up class="w-4 h-4" />
|
|
@else
|
|
<flux:icon.chevron-down class="w-4 h-4" />
|
|
@endif
|
|
@endif
|
|
</div>
|
|
</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white dark:bg-zinc-800 divide-y divide-gray-200 dark:divide-zinc-700">
|
|
@forelse($invoices as $invoice)
|
|
<tr class="hover:bg-gray-50 dark:hover:bg-zinc-700">
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{{ $invoice->invoice_number }}
|
|
</div>
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
{{ $invoice->branch->name }}
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm font-medium text-gray-900 dark:text-white">
|
|
{{ $invoice->customer->name }}
|
|
</div>
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
{{ $invoice->customer->email }}
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
|
|
{{ $invoice->invoice_date->format('M j, Y') }}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
|
|
{{ $invoice->due_date->format('M j, Y') }}
|
|
@if($invoice->isOverdue())
|
|
<span class="text-red-600 dark:text-red-400 font-medium">(Overdue)</span>
|
|
@endif
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
|
${{ number_format($invoice->total_amount, 2) }}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
|
@switch($invoice->status)
|
|
@case('draft')
|
|
bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200
|
|
@break
|
|
@case('sent')
|
|
bg-blue-100 text-blue-800 dark:bg-blue-800 dark:text-blue-200
|
|
@break
|
|
@case('paid')
|
|
bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-200
|
|
@break
|
|
@case('overdue')
|
|
bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-200
|
|
@break
|
|
@case('cancelled')
|
|
bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200
|
|
@break
|
|
@endswitch
|
|
">
|
|
{{ ucfirst($invoice->status) }}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
<div class="flex items-center justify-end space-x-2">
|
|
<flux:button size="sm" variant="ghost" href="{{ route('invoices.show', $invoice) }}" title="View">
|
|
<flux:icon.eye class="w-4 h-4" />
|
|
</flux:button>
|
|
|
|
@if($invoice->status !== 'paid')
|
|
<flux:button size="sm" variant="ghost" href="{{ route('invoices.edit', $invoice) }}" title="Edit">
|
|
<flux:icon.pencil class="w-4 h-4" />
|
|
</flux:button>
|
|
|
|
@if($invoice->status === 'draft')
|
|
<flux:button size="sm" variant="ghost" wire:click="sendInvoice({{ $invoice->id }})" title="Send">
|
|
<flux:icon.paper-airplane class="w-4 h-4" />
|
|
</flux:button>
|
|
@endif
|
|
|
|
<flux:button size="sm" variant="ghost" wire:click="markAsPaid({{ $invoice->id }})" title="Mark as Paid">
|
|
<flux:icon.check class="w-4 h-4" />
|
|
</flux:button>
|
|
@endif
|
|
|
|
@if($invoice->status === 'draft')
|
|
<flux:button size="sm" variant="ghost" wire:click="deleteInvoice({{ $invoice->id }})"
|
|
wire:confirm="Are you sure you want to delete this invoice?" title="Delete">
|
|
<flux:icon.trash class="w-4 h-4 text-red-500" />
|
|
</flux:button>
|
|
@endif
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="7" class="px-6 py-12 text-center text-gray-500 dark:text-gray-400">
|
|
<flux:icon.document-text class="w-12 h-12 mx-auto mb-4 opacity-50" />
|
|
<p class="text-lg font-medium">No invoices found</p>
|
|
<p class="mt-2">Get started by creating your first invoice.</p>
|
|
<flux:button variant="primary" href="{{ route('invoices.create') }}" class="mt-4">
|
|
Create Invoice
|
|
</flux:button>
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
@if($invoices->hasPages())
|
|
<div class="px-6 py-4 border-t border-gray-200 dark:border-zinc-700">
|
|
{{ $invoices->links() }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|