346 lines
24 KiB
PHP
346 lines
24 KiB
PHP
<div>
|
|
<!-- Workload Management Modal -->
|
|
@if($showModal)
|
|
<div class="fixed inset-0 z-50 overflow-y-auto" x-data="{ show: @entangle('showModal') }" x-show="show" x-cloak>
|
|
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
|
<div class="fixed inset-0 transition-opacity bg-zinc-500 bg-opacity-75" x-show="show" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"></div>
|
|
|
|
<span class="hidden sm:inline-block sm:align-middle sm:h-screen">​</span>
|
|
|
|
<div class="inline-block align-bottom bg-white dark:bg-zinc-800 rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-6xl sm:w-full sm:p-6" x-show="show" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100" x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between mb-6">
|
|
<flux:heading size="lg">
|
|
Workload Management - {{ $technician?->full_name }}
|
|
</flux:heading>
|
|
<flux:button variant="ghost" size="sm" icon="x-mark" wire:click="closeModal"></flux:button>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div class="space-y-6">
|
|
@if($technician)
|
|
<!-- View Controls -->
|
|
<div class="flex flex-col lg:flex-row gap-4 items-center justify-between">
|
|
<div class="flex items-center space-x-4">
|
|
<flux:select wire:model.live="periodFilter" class="w-40">
|
|
<option value="today">Today</option>
|
|
<option value="week">This Week</option>
|
|
<option value="month">This Month</option>
|
|
</flux:select> <div class="flex items-center space-x-2">
|
|
<flux:button size="sm" variant="ghost" icon="arrow-left" wire:click="previousPeriod">
|
|
</flux:button>
|
|
|
|
<div class="text-sm font-medium px-3">
|
|
@if($viewMode === 'week')
|
|
{{ \Carbon\Carbon::parse($startDate)->format('M d') }} - {{ \Carbon\Carbon::parse($endDate)->format('M d, Y') }}
|
|
@elseif($viewMode === 'month')
|
|
{{ \Carbon\Carbon::parse($startDate)->format('F Y') }}
|
|
@else
|
|
{{ \Carbon\Carbon::parse($startDate)->format('M d') }} - {{ \Carbon\Carbon::parse($endDate)->format('M d, Y') }}
|
|
@endif
|
|
</div>
|
|
|
|
<flux:button size="sm" variant="ghost" icon="arrow-right" wire:click="nextPeriod">
|
|
</flux:button>
|
|
</div>
|
|
</div>
|
|
|
|
@if($viewMode === 'custom')
|
|
<div class="flex gap-2">
|
|
<flux:input type="date" wire:model.live="startDate" />
|
|
<flux:input type="date" wire:model.live="endDate" />
|
|
</div>
|
|
@endif
|
|
|
|
<flux:button variant="primary" icon="plus" wire:click="addWorkloadRecord">
|
|
Add Record
|
|
</flux:button>
|
|
</div>
|
|
|
|
<!-- Workload Stats Summary -->
|
|
@if($workloadStats['total_scheduled'] > 0)
|
|
<div>
|
|
<flux:heading size="lg" class="mb-4">Workload Summary</flux:heading>
|
|
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-4">
|
|
<div class="bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg p-4 text-center">
|
|
<div class="text-lg font-bold text-blue-600">{{ $workloadStats['total_scheduled'] }}h</div>
|
|
<div class="text-xs text-zinc-500">Scheduled</div>
|
|
</div>
|
|
<div class="bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg p-4 text-center">
|
|
<div class="text-lg font-bold text-green-600">{{ $workloadStats['total_actual'] }}h</div>
|
|
<div class="text-xs text-zinc-500">Actual</div>
|
|
</div>
|
|
<div class="bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg p-4 text-center">
|
|
<div class="text-lg font-bold text-orange-600">{{ $workloadStats['total_overtime'] }}h</div>
|
|
<div class="text-xs text-zinc-500">Overtime</div>
|
|
</div>
|
|
<div class="bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg p-4 text-center">
|
|
<div class="text-lg font-bold text-purple-600">{{ $workloadStats['avg_utilization'] }}%</div>
|
|
<div class="text-xs text-zinc-500">Avg Utilization</div>
|
|
</div>
|
|
<div class="bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg p-4 text-center">
|
|
<div class="text-lg font-bold text-indigo-600">{{ $workloadStats['avg_efficiency'] }}%</div>
|
|
<div class="text-xs text-zinc-500">Avg Efficiency</div>
|
|
</div>
|
|
<div class="bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg p-4 text-center">
|
|
<div class="text-lg font-bold text-blue-600">{{ $workloadStats['total_jobs_assigned'] }}</div>
|
|
<div class="text-xs text-zinc-500">Jobs Assigned</div>
|
|
</div>
|
|
<div class="bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg p-4 text-center">
|
|
<div class="text-lg font-bold text-green-600">{{ $workloadStats['total_jobs_completed'] }}</div>
|
|
<div class="text-xs text-zinc-500">Jobs Completed</div>
|
|
</div>
|
|
<div class="bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg p-4 text-center">
|
|
<div class="text-lg font-bold text-emerald-600">{{ $workloadStats['completion_rate'] }}%</div>
|
|
<div class="text-xs text-zinc-500">Completion Rate</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Workload Calendar/Grid View -->
|
|
@if($filteredWorkloads->count() > 0)
|
|
<div>
|
|
<flux:heading size="lg" class="mb-4">Daily Workload</flux:heading>
|
|
|
|
@if($viewMode === 'week')
|
|
<!-- Week Grid View -->
|
|
<div class="grid grid-cols-7 gap-2">
|
|
@for($i = 0; $i < 7; $i++)
|
|
@php
|
|
$date = \Carbon\Carbon::parse($startDate)->addDays($i);
|
|
$workload = $filteredWorkloads->where('workload_date', $date->format('Y-m-d'))->first();
|
|
@endphp
|
|
<div class="border rounded-lg p-3 min-h-32 {{ $date->isToday() ? 'bg-blue-50 border-blue-200' : 'bg-white' }}">
|
|
<div class="text-sm font-medium mb-2">
|
|
{{ $date->format('D') }}
|
|
<div class="text-xs text-zinc-500">{{ $date->format('M d') }}</div>
|
|
</div>
|
|
@if($workload)
|
|
<div class="space-y-1 text-xs">
|
|
<div class="flex justify-between">
|
|
<span>Hours:</span>
|
|
<span class="font-medium">{{ $workload->actual_hours }}/{{ $workload->scheduled_hours }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span>Jobs:</span>
|
|
<span class="font-medium">{{ $workload->jobs_completed }}/{{ $workload->jobs_assigned }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span>Util:</span>
|
|
<span class="font-medium {{ $workload->utilization_rate >= 80 ? 'text-green-600' : ($workload->utilization_rate >= 60 ? 'text-yellow-600' : 'text-red-600') }}">
|
|
{{ number_format($workload->utilization_rate, 1) }}%
|
|
</span>
|
|
</div>
|
|
<div class="flex space-x-1 mt-2">
|
|
<flux:button size="sm" variant="ghost" icon="pencil" wire:click="editWorkload({{ $workload->id }})">
|
|
</flux:button>
|
|
<flux:button size="sm" variant="ghost" icon="trash"
|
|
wire:click="deleteWorkload({{ $workload->id }})"
|
|
wire:confirm="Delete this workload record?">
|
|
</flux:button>
|
|
</div>
|
|
</div>
|
|
@else
|
|
<div class="text-xs text-zinc-400">No data</div>
|
|
@endif
|
|
</div>
|
|
@endfor
|
|
</div>
|
|
@else
|
|
<!-- Table View for Month/Custom -->
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full">
|
|
<thead>
|
|
<tr class="border-b border-zinc-200 dark:border-zinc-700">
|
|
<th class="text-left py-3 px-4">Date</th>
|
|
<th class="text-left py-3 px-4">Scheduled</th>
|
|
<th class="text-left py-3 px-4">Actual</th>
|
|
<th class="text-left py-3 px-4">Overtime</th>
|
|
<th class="text-left py-3 px-4">Jobs</th>
|
|
<th class="text-left py-3 px-4">Utilization</th>
|
|
<th class="text-left py-3 px-4">Efficiency</th>
|
|
<th class="text-left py-3 px-4">Notes</th>
|
|
<th class="text-left py-3 px-4">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
@foreach($filteredWorkloads as $workload)
|
|
<tr class="border-b border-zinc-200 dark:border-zinc-700 hover:bg-zinc-50 dark:hover:bg-zinc-800">
|
|
<td class="py-3 px-4">
|
|
<div class="font-medium">{{ $workload->workload_date->format('M d, Y') }}</div>
|
|
<div class="text-xs text-zinc-500">{{ $workload->workload_date->format('D') }}</div>
|
|
</td>
|
|
<td class="py-3 px-4">{{ $workload->scheduled_hours }}h</td>
|
|
<td class="py-3 px-4">
|
|
<span class="font-medium">{{ $workload->actual_hours }}h</span>
|
|
</td>
|
|
<td class="py-3 px-4">
|
|
@if($workload->overtime_hours > 0)
|
|
<span class="text-orange-600 font-medium">{{ $workload->overtime_hours }}h</span>
|
|
@else
|
|
<span class="text-zinc-400">-</span>
|
|
@endif
|
|
</td>
|
|
<td class="py-3 px-4">
|
|
<div class="text-sm">
|
|
<span class="font-medium">{{ $workload->jobs_completed }}</span>
|
|
<span class="text-zinc-500">/ {{ $workload->jobs_assigned }}</span>
|
|
</div>
|
|
</td>
|
|
<td class="py-3 px-4">
|
|
<div class="flex items-center space-x-2">
|
|
<span class="text-sm font-medium {{ $workload->utilization_rate >= 80 ? 'text-green-600' : ($workload->utilization_rate >= 60 ? 'text-yellow-600' : 'text-red-600') }}">
|
|
{{ number_format($workload->utilization_rate, 1) }}%
|
|
</span>
|
|
<div class="w-16 bg-zinc-200 rounded-full h-2">
|
|
<div class="h-2 rounded-full {{ $workload->utilization_rate >= 80 ? 'bg-green-500' : ($workload->utilization_rate >= 60 ? 'bg-yellow-500' : 'bg-red-500') }}"
|
|
style="width: {{ min($workload->utilization_rate, 100) }}%"></div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="py-3 px-4">
|
|
<span class="text-sm font-medium {{ $workload->efficiency_rate >= 80 ? 'text-green-600' : ($workload->efficiency_rate >= 60 ? 'text-yellow-600' : 'text-red-600') }}">
|
|
{{ number_format($workload->efficiency_rate, 1) }}%
|
|
</span>
|
|
</td>
|
|
<td class="py-3 px-4">
|
|
@if($workload->notes)
|
|
<div class="truncate max-w-24" title="{{ $workload->notes }}">
|
|
{{ $workload->notes }}
|
|
</div>
|
|
@else
|
|
<span class="text-zinc-400">-</span>
|
|
@endif
|
|
</td>
|
|
<td class="py-3 px-4">
|
|
<div class="flex space-x-1">
|
|
<flux:button size="sm" variant="ghost" icon="pencil"
|
|
wire:click="editWorkload({{ $workload->id }})">
|
|
</flux:button>
|
|
<flux:button size="sm" variant="ghost" icon="trash"
|
|
wire:click="deleteWorkload({{ $workload->id }})"
|
|
wire:confirm="Delete this workload record?">
|
|
</flux:button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@else
|
|
<div class="text-center py-8 border-2 border-dashed border-zinc-300 rounded-lg">
|
|
<flux:icon.clock class="w-12 h-12 text-zinc-400 mx-auto mb-4" />
|
|
<div class="text-zinc-600 mb-2">No workload records found</div>
|
|
<div class="text-zinc-500 mb-4">Start tracking daily workload by adding records.</div>
|
|
<flux:button variant="primary" size="sm" icon="plus" wire:click="addWorkloadRecord">
|
|
Add Workload Record
|
|
</flux:button>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Workload Form -->
|
|
@if($editing !== false || $scheduled_hours > 0)
|
|
<div class="border-t pt-6">
|
|
<flux:heading size="lg" class="mb-4">
|
|
{{ $editing ? 'Edit Workload Record' : 'Add Workload Record' }}
|
|
</flux:heading>
|
|
|
|
<form wire:submit="saveWorkload" class="space-y-4">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Date</flux:label>
|
|
<flux:input type="date" wire:model="workload_date" />
|
|
<flux:error name="workload_date" />
|
|
</flux:field>
|
|
</div>
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Scheduled Hours</flux:label>
|
|
<flux:input type="number" step="0.5" wire:model="scheduled_hours" placeholder="8.0" />
|
|
<flux:error name="scheduled_hours" />
|
|
</flux:field>
|
|
</div>
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Actual Hours</flux:label>
|
|
<flux:input type="number" step="0.5" wire:model="actual_hours" placeholder="0.0" />
|
|
<flux:error name="actual_hours" />
|
|
</flux:field>
|
|
</div>
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Overtime Hours</flux:label>
|
|
<flux:input type="number" step="0.5" wire:model="overtime_hours" placeholder="0.0" />
|
|
<flux:error name="overtime_hours" />
|
|
</flux:field>
|
|
</div>
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Jobs Assigned</flux:label>
|
|
<flux:input type="number" wire:model="jobs_assigned" placeholder="0" />
|
|
<flux:error name="jobs_assigned" />
|
|
</flux:field>
|
|
</div>
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Jobs Completed</flux:label>
|
|
<flux:input type="number" wire:model="jobs_completed" placeholder="0" />
|
|
<flux:error name="jobs_completed" />
|
|
</flux:field>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<flux:field>
|
|
<flux:label>Notes</flux:label>
|
|
<flux:textarea wire:model="notes" placeholder="Additional notes about this workload..." rows="3" />
|
|
<flux:error name="notes" />
|
|
</flux:field>
|
|
</div>
|
|
|
|
<div class="flex space-x-2">
|
|
<flux:button type="submit" variant="primary">
|
|
{{ $editing ? 'Update Record' : 'Add Record' }}
|
|
</flux:button>
|
|
<flux:button type="button" variant="ghost" wire:click="resetForm">
|
|
Cancel
|
|
</flux:button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
@endif
|
|
@endif
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="mt-6 flex justify-end space-x-3">
|
|
<flux:button variant="ghost" wire:click="closeModal">Close</flux:button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Success Message -->
|
|
@if (session()->has('message'))
|
|
<div x-data="{ show: true }"
|
|
x-show="show"
|
|
x-transition
|
|
x-init="setTimeout(() => show = false, 3000)"
|
|
class="fixed top-4 right-4 z-50">
|
|
<div class="bg-green-50 border-green-200 border rounded-lg p-4">
|
|
<div class="flex items-center space-x-2 text-green-800">
|
|
<flux:icon.check-circle class="w-5 h-5" />
|
|
<span>{{ session('message') }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|