- 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.
253 lines
14 KiB
PHP
253 lines
14 KiB
PHP
<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">Stock Movements</h1>
|
||
<p class="mt-2 text-lg text-zinc-600 dark:text-zinc-400 dark:text-gray-300">
|
||
Track {{ number_format($movements->total()) }} inventory transactions and adjustments
|
||
</p>
|
||
</div>
|
||
<div class="flex flex-col sm:flex-row gap-3">
|
||
<flux:button wire:navigate href="{{ route('inventory.parts.index') }}" variant="outline" icon="cube" size="sm">
|
||
View Parts
|
||
</flux:button>
|
||
<flux:button wire:navigate href="{{ route('inventory.stock-movements.create') }}" variant="primary" icon="plus" size="sm">
|
||
Record Movement
|
||
</flux:button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Enhanced Filters -->
|
||
<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 Movements</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 or number..."
|
||
icon="magnifying-glass"
|
||
/>
|
||
</flux:field>
|
||
</div>
|
||
|
||
<!-- Movement Type Filter -->
|
||
<div>
|
||
<flux:field>
|
||
<flux:label>Movement Type</flux:label>
|
||
<flux:select wire:model.live="typeFilter">
|
||
<option value="">All Types</option>
|
||
<option value="in">📈 Stock In</option>
|
||
<option value="out">📉 Stock Out</option>
|
||
<option value="adjustment">⚖️ Adjustment</option>
|
||
<option value="transfer">🔄 Transfer</option>
|
||
<option value="return">↩️ Return</option>
|
||
</flux:select>
|
||
</flux:field>
|
||
</div>
|
||
|
||
<!-- Part Filter -->
|
||
<div>
|
||
<flux:field>
|
||
<flux:label>Specific Part</flux:label>
|
||
<flux:select wire:model.live="partFilter">
|
||
<option value="">All Parts</option>
|
||
@foreach($parts as $part)
|
||
<option value="{{ $part->id }}">{{ $part->name }} ({{ $part->part_number }})</option>
|
||
@endforeach
|
||
</flux:select>
|
||
</flux:field>
|
||
</div>
|
||
|
||
<!-- Date From -->
|
||
<div>
|
||
<flux:field>
|
||
<flux:label>From Date</flux:label>
|
||
<flux:input wire:model.live="dateFrom" type="date" />
|
||
</flux:field>
|
||
</div>
|
||
|
||
<!-- Date To -->
|
||
<div>
|
||
<flux:field>
|
||
<flux:label>To Date</flux:label>
|
||
<flux:input wire:model.live="dateTo" type="date" />
|
||
</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($typeFilter)
|
||
<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">
|
||
Type: {{ ucfirst($typeFilter) }}
|
||
<button wire:click="$set('typeFilter', '')" class="ml-2 text-green-600 hover:text-green-800">×</button>
|
||
</span>
|
||
@endif
|
||
@if($partFilter)
|
||
<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">
|
||
Part: {{ $parts->firstWhere('id', $partFilter)?->name ?? 'Unknown' }}
|
||
<button wire:click="$set('partFilter', '')" class="ml-2 text-purple-600 hover:text-purple-800">×</button>
|
||
</span>
|
||
@endif
|
||
@if($dateFrom)
|
||
<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">
|
||
From: {{ $dateFrom }}
|
||
<button wire:click="$set('dateFrom', '')" class="ml-2 text-orange-600 hover:text-orange-800">×</button>
|
||
</span>
|
||
@endif
|
||
@if($dateTo)
|
||
<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">
|
||
To: {{ $dateTo }}
|
||
<button wire:click="$set('dateTo', '')" class="ml-2 text-orange-600 hover:text-orange-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 {{ $movements->firstItem() ?? 0 }} to {{ $movements->lastItem() ?? 0 }} of {{ number_format($movements->total()) }} movements
|
||
</div>
|
||
<div class="flex items-center space-x-2">
|
||
<span>Per page:</span>
|
||
<flux:select wire:model.live="perPage" class="w-20">
|
||
<option value="15">15</option>
|
||
<option value="25">25</option>
|
||
<option value="50">50</option>
|
||
<option value="100">100</option>
|
||
</flux:select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Date To -->
|
||
<div>
|
||
<flux:input wire:model.live="dateTo" type="date" placeholder="To date" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-4">
|
||
<flux:button wire:click="clearFilters" variant="subtle">
|
||
Clear Filters
|
||
</flux:button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Movements Table -->
|
||
<div class="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700 overflow-hidden">
|
||
<div class="overflow-x-auto">
|
||
<table class="min-w-full divide-y divide-zinc-200 dark:divide-zinc-700">
|
||
<thead class="bg-zinc-50 dark:bg-zinc-900">
|
||
<tr>
|
||
<th wire:click="sortBy('created_at')" class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 dark:text-gray-400 uppercase tracking-wider cursor-pointer hover:bg-zinc-100 dark:hover:bg-zinc-800">
|
||
<div class="flex items-center space-x-1">
|
||
<span>Date</span>
|
||
@if($sortBy === 'created_at')
|
||
@if($sortDirection === 'asc')
|
||
<flux:icon.chevron-up class="w-6 h-6" />
|
||
@else
|
||
<flux:icon.chevron-down class="w-6 h-6" />
|
||
@endif
|
||
@endif
|
||
</div>
|
||
</th>
|
||
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 dark:text-gray-400 uppercase tracking-wider">
|
||
Part
|
||
</th>
|
||
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 dark:text-gray-400 uppercase tracking-wider">
|
||
Type
|
||
</th>
|
||
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 dark:text-gray-400 uppercase tracking-wider">
|
||
Quantity
|
||
</th>
|
||
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 dark:text-gray-400 uppercase tracking-wider">
|
||
Reference
|
||
</th>
|
||
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 dark:text-gray-400 uppercase tracking-wider">
|
||
User
|
||
</th>
|
||
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 dark:text-gray-400 uppercase tracking-wider">
|
||
Notes
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="bg-white dark:bg-zinc-800 divide-y divide-zinc-200 dark:divide-zinc-700">
|
||
@forelse($movements as $movement)
|
||
<tr class="hover:bg-zinc-50 dark:hover:bg-zinc-700">
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-500 dark:text-zinc-400 dark:text-gray-400">
|
||
{{ $movement->created_at->format('M d, Y H:i') }}
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<div class="text-sm font-medium text-zinc-900 dark:text-white dark:text-white">{{ $movement->part->name }}</div>
|
||
<div class="text-sm text-zinc-500 dark:text-zinc-400 dark:text-gray-400">{{ $movement->part->part_number }}</div>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
@php
|
||
$typeColors = [
|
||
'in' => 'green',
|
||
'out' => 'red',
|
||
'adjustment' => 'blue',
|
||
'transfer' => 'purple',
|
||
'return' => 'orange'
|
||
];
|
||
@endphp
|
||
<flux:badge size="sm" color="{{ $typeColors[$movement->movement_type] ?? 'gray' }}">
|
||
{{ ucfirst($movement->movement_type) }}
|
||
</flux:badge>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||
<span class="{{ $movement->movement_type === 'in' ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400' }}">
|
||
{{ $movement->movement_type === 'in' ? '+' : '-' }}{{ number_format($movement->quantity) }}
|
||
</span>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-500 dark:text-zinc-400 dark:text-gray-400">
|
||
{{ $movement->reference_type ?? 'Manual' }}
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-500 dark:text-zinc-400 dark:text-gray-400">
|
||
{{ $movement->createdBy->name ?? 'System' }}
|
||
</td>
|
||
<td class="px-6 py-4 text-sm text-zinc-500 dark:text-zinc-400 dark:text-gray-400 max-w-xs truncate">
|
||
{{ $movement->notes }}
|
||
</td>
|
||
</tr>
|
||
@empty
|
||
<tr>
|
||
<td colspan="7" class="px-6 py-12 text-center">
|
||
<div class="text-zinc-500 dark:text-zinc-400 dark:text-gray-400">
|
||
<flux:icon.clipboard-document-list class="mx-auto h-12 w-12 mb-4 opacity-40" />
|
||
<p class="text-lg font-medium">No stock movements found</p>
|
||
<p class="text-sm">Stock movements will appear here as inventory changes occur.</p>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
@endforelse
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Pagination -->
|
||
@if($movements->hasPages())
|
||
<div class="bg-zinc-50 dark:bg-zinc-900 px-6 py-3 border-t border-zinc-200 dark:border-zinc-700">
|
||
{{ $movements->links() }}
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|