sackey e3b2b220d2
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
Enhance UI and functionality across various components
- 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.
2025-08-16 14:36:58 +00:00

396 lines
24 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div class="space-y-6">
<!-- Enhanced Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 class="text-3xl font-bold text-zinc-900 dark:text-white dark:text-white">Parts Catalog</h1>
<p class="mt-2 text-lg text-zinc-600 dark:text-zinc-400 dark:text-gray-300">
Manage your inventory with {{ number_format($parts->total()) }} total parts
</p>
</div>
<div class="flex flex-col sm:flex-row gap-3">
<flux:button wire:navigate href="{{ route('inventory.stock-movements.create') }}" variant="outline" icon="clipboard-document-list" size="sm">
Record Movement
</flux:button>
<flux:button wire:navigate href="{{ route('inventory.parts.create') }}" variant="primary" icon="plus" size="sm">
Add New Part
</flux:button>
</div>
</div>
<!-- Enhanced Filters with Better Layout -->
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700 border border-zinc-200 dark:border-zinc-700">
<div class="p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-zinc-900 dark:text-white dark:text-white">Filter & Search</h3>
<flux:button wire:click="clearFilters" variant="outline" size="xs">
Clear All
</flux:button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 gap-4">
<!-- Enhanced Search -->
<div class="lg:col-span-2">
<flux:field>
<flux:label>Search Parts</flux:label>
<flux:input
wire:model.live.debounce.300ms="search"
placeholder="Part name, number, or description..."
icon="magnifying-glass"
/>
</flux:field>
</div>
<!-- Category Filter with Flux Select -->
<div>
<flux:field>
<flux:label>Category</flux:label>
<flux:select wire:model.live="categoryFilter">
<option value="">All Categories</option>
@foreach($categories as $category)
<option value="{{ $category }}">{{ ucfirst(str_replace('_', ' ', $category)) }}</option>
@endforeach
</flux:select>
</flux:field>
</div>
<!-- Stock Status Filter -->
<div>
<flux:field>
<flux:label>Stock Status</flux:label>
<flux:select wire:model.live="stockFilter">
<option value="">All Stock Levels</option>
<option value="in_stock"> In Stock</option>
<option value="low_stock">⚠️ Low Stock</option>
<option value="out_of_stock"> Out of Stock</option>
<option value="overstock">📈 Overstock</option>
</flux:select>
</flux:field>
</div>
<!-- Supplier Filter -->
<div>
<flux:field>
<flux:label>Supplier</flux:label>
<flux:select wire:model.live="supplierFilter">
<option value="">All Suppliers</option>
@foreach($suppliers as $supplier)
<option value="{{ $supplier->id }}">{{ $supplier->name }}</option>
@endforeach
</flux:select>
</flux:field>
</div>
<!-- Sort Options -->
<div>
<flux:field>
<flux:label>Sort By</flux:label>
<flux:select wire:model.live="sortField">
<option value="name">Name A-Z</option>
<option value="part_number">Part Number</option>
<option value="quantity_on_hand">Stock Level</option>
<option value="cost_price">Cost Price</option>
<option value="created_at">Date Added</option>
</flux:select>
</flux:field>
</div>
</div>
<!-- Active Filters Display -->
<div class="mt-4 flex flex-wrap gap-2">
@if($search)
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
Search: "{{ $search }}"
<button wire:click="$set('search', '')" class="ml-2 text-blue-600 hover:text-blue-800">×</button>
</span>
@endif
@if($categoryFilter)
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
Category: {{ ucfirst(str_replace('_', ' ', $categoryFilter)) }}
<button wire:click="$set('categoryFilter', '')" class="ml-2 text-green-600 hover:text-green-800">×</button>
</span>
@endif
@if($stockFilter)
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200">
Stock: {{ ucfirst(str_replace('_', ' ', $stockFilter)) }}
<button wire:click="$set('stockFilter', '')" class="ml-2 text-orange-600 hover:text-orange-800">×</button>
</span>
@endif
@if($supplierFilter)
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200">
Supplier: {{ $suppliers->firstWhere('id', $supplierFilter)?->name ?? 'Unknown' }}
<button wire:click="$set('supplierFilter', '')" class="ml-2 text-purple-600 hover:text-purple-800">×</button>
</span>
@endif
</div>
</div>
</div>
<!-- Results Summary -->
<div class="flex items-center justify-between text-sm text-zinc-600 dark:text-zinc-400 dark:text-gray-400">
<div>
Showing {{ $parts->firstItem() ?? 0 }} to {{ $parts->lastItem() ?? 0 }} of {{ number_format($parts->total()) }} parts
</div>
<div class="flex items-center space-x-2">
<span>Per page:</span>
<flux:select wire:model.live="perPage" class="w-20">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</flux:select>
</div>
</div>
<!-- Enhanced Parts Table -->
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700 overflow-hidden border border-zinc-200 dark:border-zinc-700">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-zinc-200 dark:divide-zinc-700">
<thead class="bg-gradient-to-r from-gray-50 to-gray-100 dark:from-zinc-900 dark:to-zinc-800">
<tr>
<th wire:click="sortBy('part_number')" class="px-6 py-4 text-left text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors">
<div class="flex items-center space-x-1">
<span>Part #</span>
@if($sortBy === 'part_number')
<flux:icon.chevron-up class="w-6 h-6 {{ $sortDirection === 'asc' ? '' : 'rotate-180' }}" />
@endif
</div>
</th>
<th wire:click="sortBy('name')" class="px-6 py-4 text-left text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors">
<div class="flex items-center space-x-1">
<span>Part Details</span>
@if($sortBy === 'name')
<flux:icon.chevron-up class="w-6 h-6 {{ $sortDirection === 'asc' ? '' : 'rotate-180' }}" />
@endif
</div>
</th>
<th class="px-6 py-4 text-left text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">Supplier</th>
<th wire:click="sortBy('quantity_on_hand')" class="px-6 py-4 text-center text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors">
<div class="flex items-center justify-center space-x-1">
<span>Stock Level</span>
@if($sortBy === 'quantity_on_hand')
<flux:icon.chevron-up class="w-6 h-6 {{ $sortDirection === 'asc' ? '' : 'rotate-180' }}" />
@endif
</div>
</th>
<th wire:click="sortBy('cost_price')" class="px-6 py-4 text-right text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider cursor-pointer hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors">
<div class="flex items-center justify-end space-x-1">
<span>Pricing</span>
@if($sortBy === 'cost_price')
<flux:icon.chevron-up class="w-6 h-6 {{ $sortDirection === 'asc' ? '' : 'rotate-180' }}" />
@endif
</div>
</th>
<th class="px-6 py-4 text-center text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">Status</th>
<th class="px-6 py-4 text-right text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-zinc-800 divide-y divide-zinc-200 dark:divide-zinc-700">
@forelse($parts as $part)
<tr class="hover:bg-zinc-50 dark:hover:bg-zinc-700/50 transition-colors">
<!-- Part Number -->
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-bold text-zinc-900 dark:text-white dark:text-white">
{{ $part->part_number }}
</div>
@if($part->barcode)
<div class="text-xs text-zinc-500 dark:text-zinc-400 dark:text-gray-400 font-mono">
{{ $part->barcode }}
</div>
@endif
</td>
<!-- Part Details -->
<td class="px-6 py-4">
<div class="flex items-start space-x-3">
<div class="flex-1 min-w-0">
<div class="text-sm font-semibold text-zinc-900 dark:text-white dark:text-white truncate">
{{ $part->name }}
</div>
@if($part->description)
<div class="text-xs text-zinc-500 dark:text-zinc-400 dark:text-gray-400 line-clamp-2">
{{ Str::limit($part->description, 100) }}
</div>
@endif
<div class="flex items-center space-x-2 mt-1">
@if($part->category)
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
{{ ucfirst(str_replace('_', ' ', $part->category)) }}
</span>
@endif
@if($part->manufacturer)
<span class="text-xs text-zinc-500 dark:text-zinc-400 dark:text-gray-400">
by {{ $part->manufacturer }}
</span>
@endif
</div>
</div>
</div>
</td>
<!-- Supplier -->
<td class="px-6 py-4 whitespace-nowrap">
@if($part->supplier)
<div class="text-sm text-zinc-900 dark:text-white dark:text-white">
{{ $part->supplier->name }}
</div>
@if($part->supplier->contact_email)
<div class="text-xs text-zinc-500 dark:text-zinc-400 dark:text-gray-400">
{{ $part->supplier->contact_email }}
</div>
@endif
@else
<span class="text-sm text-gray-400 dark:text-zinc-500 dark:text-zinc-400">No supplier</span>
@endif
</td>
<!-- Stock Level -->
<td class="px-6 py-4 whitespace-nowrap text-center">
<div class="flex flex-col items-center">
<span class="text-lg font-bold
@if($part->quantity_on_hand <= 0) text-red-600 dark:text-red-400
@elseif($part->quantity_on_hand <= $part->minimum_stock_level) text-orange-600 dark:text-orange-400
@else text-green-600 dark:text-green-400 @endif">
{{ number_format($part->quantity_on_hand) }}
</span>
@if($part->minimum_stock_level)
<span class="text-xs text-zinc-500 dark:text-zinc-400 dark:text-gray-400">
Min: {{ $part->minimum_stock_level }}
</span>
@endif
@if($part->unit_of_measurement)
<span class="text-xs text-zinc-500 dark:text-zinc-400 dark:text-gray-400">
{{ $part->unit_of_measurement }}
</span>
@endif
</div>
</td>
<!-- Pricing -->
<td class="px-6 py-4 whitespace-nowrap text-right">
<div class="text-sm">
<div class="text-zinc-900 dark:text-white dark:text-white font-semibold">
${{ number_format($part->sell_price, 2) }}
</div>
<div class="text-zinc-500 dark:text-zinc-400 dark:text-gray-400">
Cost: ${{ number_format($part->cost_price, 2) }}
</div>
@if($part->sell_price > $part->cost_price)
<div class="text-xs text-green-600 dark:text-green-400">
{{ number_format((($part->sell_price - $part->cost_price) / $part->cost_price) * 100, 1) }}% margin
</div>
@endif
</div>
</td>
<!-- Status -->
<td class="px-6 py-4 whitespace-nowrap text-center">
@if($part->quantity_on_hand <= 0)
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200">
Out of Stock
</span>
@elseif($part->quantity_on_hand <= $part->minimum_stock_level)
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200">
Low Stock
</span>
@elseif($part->quantity_on_hand > ($part->maximum_stock_level ?? 1000))
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
Overstock
</span>
@else
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
In Stock
</span>
@endif
</td>
<!-- Actions -->
<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
wire:navigate
href="{{ route('inventory.parts.show', $part) }}"
variant="ghost"
size="xs"
icon="eye"
>
View
</flux:button>
<flux:button
wire:navigate
href="{{ route('inventory.parts.edit', $part) }}"
variant="ghost"
size="xs"
icon="pencil"
>
Edit
</flux:button>
</div>
</td>
</tr>
@empty
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-900 dark:text-white dark:text-white">
@if($part->category)
<flux:badge size="sm" color="gray">{{ ucfirst($part->category) }}</flux:badge>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-900 dark:text-white dark:text-white">
{{ $part->supplier?->full_name ?? 'No Supplier' }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center space-x-2">
<span class="text-sm font-medium text-zinc-900 dark:text-white dark:text-white">{{ number_format($part->quantity_on_hand) }}</span>
<span class="text-xs {{ $part->stock_status_color }}">
{{ ucfirst(str_replace('_', ' ', $part->stock_status)) }}
</span>
</div>
<div class="text-xs text-zinc-500 dark:text-zinc-400 dark:text-gray-400">
Min: {{ $part->minimum_stock_level }}
<tr>
<td colspan="7" class="px-6 py-16 text-center">
<div class="text-zinc-500 dark:text-zinc-400 dark:text-gray-400">
<flux:icon.cube-transparent class="mx-auto h-16 w-16 mb-6 opacity-40" />
<h3 class="text-lg font-semibold text-zinc-900 dark:text-white dark:text-white mb-2">No parts found</h3>
<p class="text-zinc-600 dark:text-zinc-400 dark:text-gray-400 mb-6">
@if($search || $categoryFilter || $stockFilter || $supplierFilter)
No parts match your current filters. Try adjusting your search criteria.
@else
Get started by adding your first part to the catalog.
@endif
</p>
<div class="flex items-center justify-center space-x-3">
@if($search || $categoryFilter || $stockFilter || $supplierFilter)
<flux:button wire:click="clearFilters" variant="outline" size="sm">
Clear Filters
</flux:button>
@endif
<flux:button wire:navigate href="{{ route('inventory.parts.create') }}" variant="primary" size="sm">
Add First Part
</flux:button>
</div>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Enhanced Pagination -->
@if($parts->hasPages())
<div class="bg-zinc-50 dark:bg-zinc-900 px-6 py-4 border-t border-zinc-200 dark:border-zinc-700">
<div class="flex items-center justify-between">
<div class="text-sm text-gray-700 dark:text-gray-300">
Showing {{ $parts->firstItem() }} to {{ $parts->lastItem() }} of {{ number_format($parts->total()) }} results
</div>
<div>
{{ $parts->links() }}
</div>
</div>
</div>
@endif
</div>
</div>
</div>