- 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.
266 lines
18 KiB
PHP
266 lines
18 KiB
PHP
<div class="space-y-6">
|
|
<!-- Header -->
|
|
<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">Edit Estimate</h1>
|
|
<p class="text-gray-600 dark:text-gray-400">
|
|
{{ $estimate->estimate_number }} •
|
|
{{ $estimate->customer_id ? $estimate->customer?->name : $estimate->jobCard?->customer?->name ?? 'Unknown Customer' }}
|
|
@if($estimate->customer_id && $estimate->vehicle)
|
|
• {{ $estimate->vehicle->year }} {{ $estimate->vehicle->make }} {{ $estimate->vehicle->model }}
|
|
@elseif($estimate->jobCard?->vehicle)
|
|
• {{ $estimate->jobCard->vehicle->year }} {{ $estimate->jobCard->vehicle->make }} {{ $estimate->jobCard->vehicle->model }}
|
|
@endif
|
|
</p>
|
|
@if($lastSaved)
|
|
<p class="text-sm text-green-600 dark:text-green-400 mt-2">
|
|
<span class="inline-flex items-center">
|
|
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
Auto-saved at {{ $lastSaved }}
|
|
</span>
|
|
</p>
|
|
@endif
|
|
</div>
|
|
<div class="flex space-x-3 mt-4 lg:mt-0">
|
|
<flux:button wire:click="toggleAutoSave" variant="ghost" size="sm">
|
|
@if($autoSave)
|
|
<svg class="w-4 h-4 mr-2 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
Auto-save ON
|
|
@else
|
|
<svg class="w-4 h-4 mr-2 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M13.477 14.89A6 6 0 015.11 6.524l8.367 8.368zm1.414-1.414L6.524 5.11a6 6 0 018.367 8.367zM18 10a8 8 0 11-16 0 8 8 0 0116 0z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
Auto-save OFF
|
|
@endif
|
|
</flux:button>
|
|
<flux:button href="{{ route('estimates.show', $estimate) }}" variant="ghost">
|
|
View Estimate
|
|
</flux:button>
|
|
<flux:button wire:click="save" variant="primary">
|
|
Save Changes
|
|
</flux:button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Add Presets -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Quick Add Service Items</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
@foreach($quickAddPresets as $key => $preset)
|
|
<button
|
|
wire:click="addQuickPreset('{{ $key }}')"
|
|
class="p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:border-accent hover:bg-accent/5 dark:hover:bg-accent/10 transition-colors text-left group"
|
|
>
|
|
<div class="font-medium text-gray-900 dark:text-gray-100 group-hover:text-accent">{{ $preset['description'] }}</div>
|
|
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1">${{ number_format($preset['unit_price'], 2) }}</div>
|
|
<div class="text-xs text-accent mt-2 opacity-0 group-hover:opacity-100 transition-opacity">Click to add</div>
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Content Grid -->
|
|
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6">
|
|
<!-- Line Items (2/3 width) -->
|
|
<div class="xl:col-span-2">
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700">
|
|
<div class="p-6 border-b border-gray-200 dark:border-gray-700">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Line Items</h3>
|
|
<div class="flex space-x-3">
|
|
@if(!$bulkOperationMode)
|
|
<flux:button wire:click="toggleBulkMode" variant="outline" size="sm">
|
|
<svg class="w-4 h-4 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>
|
|
Bulk Operations
|
|
</flux:button>
|
|
@else
|
|
<flux:button wire:click="bulkDelete" variant="danger" size="sm" :disabled="empty($selectedItems)">
|
|
<svg class="w-4 h-4 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 Selected
|
|
</flux:button>
|
|
<flux:button wire:click="toggleBulkMode" variant="ghost" size="sm">
|
|
Cancel
|
|
</flux:button>
|
|
@endif
|
|
<flux:button wire:click="addLineItem" variant="primary" size="sm">
|
|
<svg class="w-4 h-4 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>
|
|
Add Item
|
|
</flux:button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="overflow-x-auto">
|
|
@if(count($lineItems) > 0)
|
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
<thead class="bg-gray-50 dark:bg-gray-700">
|
|
<tr>
|
|
@if($bulkOperationMode)
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
<flux:checkbox wire:model.live="selectAll" />
|
|
</th>
|
|
@endif
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Type</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Description</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Qty</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Unit Price</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Total</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
@foreach($lineItems as $index => $item)
|
|
<tr wire:key="item-{{ $index }}" class="hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
@if($bulkOperationMode)
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<flux:checkbox wire:model.live="selectedItems" value="{{ $index }}" />
|
|
</td>
|
|
@endif
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<flux:select wire:model.live="lineItems.{{ $index }}.type" class="w-full">
|
|
<option value="labour">Labour</option>
|
|
<option value="parts">Parts</option>
|
|
<option value="miscellaneous">Miscellaneous</option>
|
|
</flux:select>
|
|
<flux:error name="lineItems.{{ $index }}.type" />
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<flux:input wire:model.live="lineItems.{{ $index }}.description" placeholder="Item description" class="w-full" />
|
|
<flux:error name="lineItems.{{ $index }}.description" />
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<flux:input wire:model.live="lineItems.{{ $index }}.quantity" type="number" step="0.01" min="0.01" class="w-20" />
|
|
<flux:error name="lineItems.{{ $index }}.quantity" />
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<flux:input wire:model.live="lineItems.{{ $index }}.unit_price" type="number" step="0.01" min="0" class="w-24" />
|
|
<flux:error name="lineItems.{{ $index }}.unit_price" />
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
${{ number_format(($item['quantity'] ?? 0) * ($item['unit_price'] ?? 0), 2) }}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
<flux:button wire:click="removeLineItem({{ $index }})" variant="danger" size="sm">
|
|
<svg class="w-4 h-4" 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>
|
|
</flux:button>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
@else
|
|
<div class="p-12 text-center">
|
|
<svg class="w-12 h-12 mx-auto text-gray-400 dark:text-gray-600 mb-4" 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>
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">No line items yet</h3>
|
|
<p class="text-gray-500 dark:text-gray-400 mb-4">Get started by adding your first service or part.</p>
|
|
<flux:button wire:click="addLineItem" variant="primary">
|
|
<svg class="w-4 h-4 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>
|
|
Add First Item
|
|
</flux:button>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings & Summary (1/3 width) -->
|
|
<div class="space-y-6">
|
|
<!-- Estimate Settings -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Settings</h3>
|
|
<div class="space-y-4">
|
|
<flux:field>
|
|
<flux:label>Validity Period (Days)</flux:label>
|
|
<flux:input wire:model.live="validity_period_days" type="number" min="1" max="365" />
|
|
<flux:error name="validity_period_days" />
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>Tax Rate (%)</flux:label>
|
|
<flux:input wire:model.live="tax_rate" type="number" step="0.01" min="0" max="100" />
|
|
<flux:error name="tax_rate" />
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>Discount Amount ($)</flux:label>
|
|
<flux:input wire:model.live="discount_amount" type="number" step="0.01" min="0" />
|
|
<flux:error name="discount_amount" />
|
|
</flux:field>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Summary -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Summary</h3>
|
|
<div class="space-y-3">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600 dark:text-gray-400">Subtotal:</span>
|
|
<span class="font-medium text-gray-900 dark:text-gray-100">${{ number_format($subtotal, 2) }}</span>
|
|
</div>
|
|
@if($discount_amount > 0)
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600 dark:text-gray-400">Discount:</span>
|
|
<span class="font-medium text-red-600 dark:text-red-400">-${{ number_format($discount_amount, 2) }}</span>
|
|
</div>
|
|
@endif
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-600 dark:text-gray-400">Tax ({{ $tax_rate }}%):</span>
|
|
<span class="font-medium text-gray-900 dark:text-gray-100">${{ number_format($tax_amount, 2) }}</span>
|
|
</div>
|
|
<div class="border-t border-gray-200 dark:border-gray-700 pt-3">
|
|
<div class="flex justify-between">
|
|
<span class="text-lg font-semibold text-gray-900 dark:text-gray-100">Total:</span>
|
|
<span class="text-lg font-bold text-accent">${{ number_format($total_amount, 2) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notes -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Notes</h3>
|
|
<div class="space-y-4">
|
|
<flux:field>
|
|
<flux:label>Customer Notes</flux:label>
|
|
<flux:textarea wire:model.live="notes" rows="3" placeholder="Notes visible to customer..." />
|
|
<flux:error name="notes" />
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>Internal Notes</flux:label>
|
|
<flux:textarea wire:model.live="internal_notes" rows="3" placeholder="Internal notes (not shown to customer)..." />
|
|
<flux:error name="internal_notes" />
|
|
</flux:field>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Terms & Conditions -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
<flux:field>
|
|
<flux:label>Terms & Conditions</flux:label>
|
|
<flux:textarea wire:model.live="terms_and_conditions" rows="4" placeholder="Terms and conditions..." />
|
|
<flux:error name="terms_and_conditions" />
|
|
</flux:field>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|