- 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.
225 lines
12 KiB
PHP
225 lines
12 KiB
PHP
<div>
|
|
<flux:header>
|
|
<flux:heading size="xl">Create Invoice</flux:heading>
|
|
<flux:subheading>Create a new invoice for services and parts</flux:subheading>
|
|
</flux:header>
|
|
|
|
<form wire:submit.prevent="save" class="space-y-6">
|
|
{{-- Basic Information --}}
|
|
<div class="bg-white dark:bg-zinc-800 shadow-sm rounded-lg border border-zinc-200 dark:border-zinc-700 p-6 space-y-6">
|
|
<flux:heading size="lg">Invoice Details</flux:heading>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<flux:field>
|
|
<flux:label>Customer</flux:label>
|
|
<flux:select wire:model="customer_id" placeholder="Select customer...">
|
|
@foreach($customers as $customer)
|
|
<flux:select.option value="{{ $customer->id }}">{{ $customer->name }}</flux:select.option>
|
|
@endforeach
|
|
</flux:select>
|
|
<flux:error name="customer_id" />
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>Branch</flux:label>
|
|
<flux:select wire:model="branch_id" placeholder="Select branch...">
|
|
@foreach($branches as $branch)
|
|
<flux:select.option value="{{ $branch->id }}">{{ $branch->name }}</flux:select.option>
|
|
@endforeach
|
|
</flux:select>
|
|
<flux:error name="branch_id" />
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>Invoice Date</flux:label>
|
|
<flux:input type="date" wire:model="invoice_date" />
|
|
<flux:error name="invoice_date" />
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>Due Date</flux:label>
|
|
<flux:input type="date" wire:model="due_date" />
|
|
<flux:error name="due_date" />
|
|
</flux:field>
|
|
</div>
|
|
|
|
<flux:field>
|
|
<flux:label>Description</flux:label>
|
|
<flux:textarea wire:model="description" placeholder="Brief description of work performed..." rows="2" />
|
|
<flux:error name="description" />
|
|
</flux:field>
|
|
</div>
|
|
|
|
{{-- Line Items --}}
|
|
<div class="bg-white dark:bg-zinc-800 shadow-sm rounded-lg border border-zinc-200 dark:border-zinc-700 p-6 space-y-6">
|
|
<div class="flex justify-between items-center">
|
|
<flux:heading size="lg">Line Items</flux:heading>
|
|
<flux:button wire:click="addLineItem" variant="ghost" size="sm">
|
|
<flux:icon.plus class="size-4" />
|
|
Add Item
|
|
</flux:button>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
@foreach($line_items as $index => $item)
|
|
<div class="border rounded-lg p-4 bg-zinc-50 dark:bg-zinc-900/50">
|
|
<div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-start">
|
|
{{-- Type --}}
|
|
<div class="md:col-span-2">
|
|
<flux:label>Type</flux:label>
|
|
<flux:select wire:model="line_items.{{ $index }}.type">
|
|
<flux:select.option value="labour">Labour</flux:select.option>
|
|
<flux:select.option value="parts">Parts</flux:select.option>
|
|
<flux:select.option value="miscellaneous">Miscellaneous</flux:select.option>
|
|
</flux:select>
|
|
</div>
|
|
|
|
{{-- Part Selection (only for parts) --}}
|
|
@if($item['type'] === 'parts')
|
|
<div class="md:col-span-3">
|
|
<flux:label>Part</flux:label>
|
|
<select wire:model="line_items.{{ $index }}.part_id" class="w-full rounded-md border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100 shadow-sm focus:border-blue-500 focus:ring-blue-500">
|
|
<option value="">Select part...</option>
|
|
@if(count($parts) === 0)
|
|
<option value="" disabled>No parts available</option>
|
|
@endif
|
|
@foreach($parts as $part)
|
|
<option value="{{ $part->id }}">{{ $part->name }} ({{ $part->part_number }}) - ${{ number_format($part->sell_price, 2) }}</option>
|
|
@endforeach
|
|
</select>
|
|
<small class="text-zinc-500">{{ count($parts) }} parts available</small>
|
|
</div>
|
|
@else
|
|
<div class="md:col-span-3">
|
|
<flux:label>Description</flux:label>
|
|
<flux:input wire:model="line_items.{{ $index }}.description" placeholder="Item description..." />
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Quantity --}}
|
|
<div class="md:col-span-2">
|
|
<flux:label>Quantity</flux:label>
|
|
<flux:input type="number" wire:model="line_items.{{ $index }}.quantity" min="1" step="1" />
|
|
</div>
|
|
|
|
{{-- Unit Price --}}
|
|
<div class="md:col-span-2">
|
|
<flux:label>Unit Price</flux:label>
|
|
<flux:input type="number" wire:model="line_items.{{ $index }}.unit_price" min="0" step="0.01" />
|
|
</div>
|
|
|
|
{{-- Total --}}
|
|
<div class="md:col-span-2">
|
|
<flux:label>Total</flux:label>
|
|
<div class="px-3 py-2 bg-white dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-600 rounded text-sm">
|
|
${{ number_format(($item['quantity'] ?? 0) * ($item['unit_price'] ?? 0), 2) }}
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Remove Button --}}
|
|
<div class="md:col-span-1 flex items-end">
|
|
@if(count($line_items) > 1)
|
|
<flux:button wire:click="removeLineItem({{ $index }})" variant="danger" size="sm">
|
|
<flux:icon.trash class="size-4" />
|
|
</flux:button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Additional fields for parts --}}
|
|
@if($item['type'] === 'parts')
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
|
<flux:field>
|
|
<flux:label>Part Number</flux:label>
|
|
<flux:input wire:model="line_items.{{ $index }}.part_number" placeholder="Part number..." />
|
|
</flux:field>
|
|
<flux:field>
|
|
<flux:label>Technical Notes</flux:label>
|
|
<flux:input wire:model="line_items.{{ $index }}.technical_notes" placeholder="Technical notes..." />
|
|
</flux:field>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Technical notes for labour --}}
|
|
@if($item['type'] === 'labour')
|
|
<div class="mt-4">
|
|
<flux:field>
|
|
<flux:label>Technical Notes</flux:label>
|
|
<flux:input wire:model="line_items.{{ $index }}.technical_notes" placeholder="Technical notes..." />
|
|
</flux:field>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
<flux:error name="line_items" />
|
|
</div>
|
|
|
|
{{-- Totals and Additional Info --}}
|
|
<div class="bg-white dark:bg-zinc-800 shadow-sm rounded-lg border border-zinc-200 dark:border-zinc-700 p-6 space-y-6">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
{{-- Additional Information --}}
|
|
<div class="space-y-4">
|
|
<flux:field>
|
|
<flux:label>Tax Rate (%)</flux:label>
|
|
<flux:input type="number" wire:model="tax_rate" min="0" max="100" step="0.01" />
|
|
<flux:error name="tax_rate" />
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>Discount Amount</flux:label>
|
|
<flux:input type="number" wire:model="discount_amount" min="0" step="0.01" />
|
|
<flux:error name="discount_amount" />
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>Notes</flux:label>
|
|
<flux:textarea wire:model="notes" placeholder="Additional notes..." rows="3" />
|
|
<flux:error name="notes" />
|
|
</flux:field>
|
|
</div>
|
|
|
|
{{-- Totals Summary --}}
|
|
<div class="bg-zinc-50 dark:bg-zinc-900/50 rounded-lg p-4">
|
|
<flux:heading size="base" class="mb-4">Invoice Summary</flux:heading>
|
|
|
|
<div class="space-y-2 text-sm">
|
|
<div class="flex justify-between">
|
|
<span>Subtotal:</span>
|
|
<span>${{ number_format($this->calculateSubtotal(), 2) }}</span>
|
|
</div>
|
|
@if($discount_amount > 0)
|
|
<div class="flex justify-between text-red-600">
|
|
<span>Discount:</span>
|
|
<span>-${{ number_format($discount_amount, 2) }}</span>
|
|
</div>
|
|
@endif
|
|
<div class="flex justify-between">
|
|
<span>Tax ({{ $tax_rate }}%):</span>
|
|
<span>${{ number_format($this->calculateTax(), 2) }}</span>
|
|
</div>
|
|
<hr class="my-2">
|
|
<div class="flex justify-between font-semibold text-lg">
|
|
<span>Total:</span>
|
|
<span>${{ number_format($this->calculateTotal(), 2) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<flux:field>
|
|
<flux:label>Terms and Conditions</flux:label>
|
|
<flux:textarea wire:model="terms_and_conditions" placeholder="Payment terms and conditions..." rows="3" />
|
|
<flux:error name="terms_and_conditions" />
|
|
</flux:field>
|
|
</div>
|
|
|
|
{{-- Actions --}}
|
|
<div class="flex justify-end space-x-3">
|
|
<flux:button variant="ghost" href="{{ route('invoices.index') }}">Cancel</flux:button>
|
|
<flux:button type="submit" variant="primary">Create Invoice</flux:button>
|
|
</div>
|
|
</form>
|
|
</div>
|