started_at->toISOString() . "'" : 'null' }},
taskId: '',
jobId: '',
taskErr: false,
jobErr: false,
isTradesUser: {{ in_array(auth()->user()->role?->name, ['trades']) ? 'true' : 'false' }},
isFactoryEmployee: {{ $isFactoryEmployee ? 'true' : 'false' }},
jobTaskMap: window.__timesheetJobTaskMap,
allTaskTypes: window.__timesheetAllTaskTypes,
get filteredTaskTypes() {
if (!this.isFactoryEmployee) return this.allTaskTypes;
if (!this.jobId) return [];
const allowed = this.jobTaskMap[this.jobId] || [];
return this.allTaskTypes.filter(t => allowed.includes(t.id));
},
tick() {
if (!this.startedAt) return;
const diff = Math.floor((Date.now() - new Date(this.startedAt)) / 1000);
const h = Math.floor(diff/3600), m = Math.floor((diff%3600)/60), s = diff%60;
this.elapsed = (h>0?h+'h ':'')+m+'m '+s+'s';
},
tryStart() {
this.taskErr = !this.isTradesUser && !this.taskId;
this.jobErr = !this.jobId;
if (this.taskErr || this.jobErr) return;
this.running = true;
$el.querySelector('form[data-timer-form]').submit();
}
}" x-init="if(running) setInterval(()=>tick(), 1000)">
My Timesheet
@if(in_array(auth()->user()->role?->name, ['office_admin','project_manager']))
All Staff
@endif
{{-- ── Weekly Summary (non-factory-employee users including processor and trades) ── --}}
@if(!$isFactoryEmployee)
This Week Hours
{{ number_format($weeklyHours, 1) }}h
{{ Carbon\Carbon::now()->startOfWeek()->format('M j') }} – {{ Carbon\Carbon::now()->endOfWeek()->format('M j') }}
@if(auth()->user()->hourly_rate)
This Week Earnings
${{ number_format($weeklyEarnings, 2) }}
@ ${{ number_format(auth()->user()->hourly_rate, 2) }}/hr
Hourly Rate
${{ number_format(auth()->user()->hourly_rate, 2) }}
per hour
@else
Rate
No hourly rate set — contact admin
@endif
@endif
{{-- ── Generic Timer Panel (all non-employee, non-trades, non-processor roles) ── --}}
@if(!$isFactoryEmployee && !$isTrades && !$isProcessor)
Timer
{{ $running?->elapsed ?? '0m' }}
Timer running
@if($running?->taskType) — {{ $running->taskType->name }} @endif
@if($running?->job) — {{ $running->job->job_number }} @endif
@if((auth()->user()->role?->name !== 'trades' && $errors->has('task_type_id')) || $errors->has('job_id'))
@foreach(auth()->user()->role?->name === 'trades' ? ['job_id'] : ['task_type_id','job_id'] as $field)
@error($field)
{{ $message }}
@enderror
@endforeach
@endif
@endif
{{-- ── Trades: Job-wise Timer Cards ── --}}
@if($isTrades)
@if(session('success'))
{{ session('success') }}
@endif
@if(session('error'))
{{ session('error') }}
@endif
@if($jobs->count() > 0)
My Assigned Jobs
Start, pause, resume and complete your time for each assigned job
My Invoices
@foreach($jobs as $job)
@php
$tradeTimerKey = $job->id . ':';
$tradeRunningEntry = ($runningEntries ?? collect())->where('job_id', $job->id)->first();
$tradePausedEntry = ($pausedEntries ?? collect())->where('job_id', $job->id)->first();
$isRunningNow = $tradeRunningEntry !== null;
$isPausedNow = $tradePausedEntry !== null && !$isRunningNow;
$hasInProgress = $isRunningNow || $isPausedNow;
$loggedMins = \App\Models\TimeEntry::where('user_id', auth()->id())
->where('job_id', $job->id)->whereIn('status', ['paused', 'completed'])->sum('duration_minutes');
$runningMins = $isRunningNow ? $tradeRunningEntry->started_at->diffInMinutes(now()) : 0;
$totalHrs = round(($loggedMins + $runningMins) / 60, 2);
$uninvoicedMins = \App\Models\TimeEntry::where('user_id', auth()->id())
->where('job_id', $job->id)->where('status', 'completed')->whereNull('invoiced_in')->sum('duration_minutes');
$uninvoicedHrs = round($uninvoicedMins / 60, 2);
$hasUninvoiced = $uninvoicedMins > 0;
$invoicedMins = \App\Models\TimeEntry::where('user_id', auth()->id())
->where('job_id', $job->id)->where('status', 'completed')->whereNotNull('invoiced_in')->sum('duration_minutes');
$invoicedHrs = round($invoicedMins / 60, 2);
$isinvoicedCompleted = \App\Models\TimeEntry::where('user_id', auth()->id())->where('job_id', $job->id)->first();
$isinvoicedthejob = \App\Models\StaffInvoice::where('user_id', auth()->id())->where('job_id', $job->id)->count();
@endphp
@if($isinvoicedthejob < 1)
{{ $job->job_number }}
{{ $job->contact?->first_name }} {{ $job->contact?->last_name }}
@if($isRunningNow)
{{ $tradeRunningEntry->elapsed }}
@endif
Total logged: {{ $totalHrs }}h
@if($hasUninvoiced){{ $uninvoicedHrs }}h ready for invoice@endif
@if($invoicedHrs > 0){{ $invoicedHrs }}h invoiced@endif
@if(auth()->user()->hourly_rate && $uninvoicedHrs > 0)
Est. ${{ number_format($uninvoicedHrs * auth()->user()->hourly_rate, 2) }} (@ ${{ number_format(auth()->user()->hourly_rate, 2) }}/hr)
@endif
@if($isRunningNow)
@elseif($isPausedNow)
@else
@if($isinvoicedCompleted?->status != 'completed')
@endif
@endif
@if($hasInProgress)
@endif
@if($hasUninvoiced && !$hasInProgress)
@endif
@endif
@endforeach
@else
No jobs are currently assigned to you.
Contact admin to get assigned to jobs.
@endif
@endif {{-- end $isTrades --}}
{{-- ══════════════════════════════════════════════════════════════
── Processor: Multi-Task Timer View ──
Processor can run, pause, resume and complete multiple tasks
simultaneously (no assignment table — free-form task + job).
Completed entries are direct-role, so they immediately affect
the gross profit report for each job.
══════════════════════════════════════════════════════════════ --}}
@if($isProcessor)
@if(session('success'))
{{ session('success') }}
@endif
@if(session('error'))
{{ session('error') }}
@endif
{{-- Live per-task timer ticker for processor (key = job_id:task_type_id) --}}
{{-- Start New Task Form --}}
Start a Task Timer
You can run multiple task timers at the same time. Each task is tracked independently per job and directly affects the gross profit report when completed.
@error('error')
{{ $message }}
@enderror
{{-- Running Task Cards --}}
@if(($runningEntries ?? collect())->count() > 0)
Running Timers
{{ ($runningEntries ?? collect())->count() }} active
@foreach($runningEntries ?? collect() as $runEntry)
@php $procTimerKey = $runEntry->job_id . ':' . $runEntry->task_type_id; @endphp
{{ $runEntry->taskType?->name ?? '—' }}
{{ $runEntry->job?->job_number ?? '—' }}
@if($runEntry->job?->contact)· {{ $runEntry->job->contact->first_name }} {{ $runEntry->job->contact->last_name }}@endif
{{ $runEntry->elapsed }}
@endforeach
@endif
{{-- Paused Task Cards (unique job+task combos that aren't currently running) --}}
@php
$procRunningKeys = ($runningEntries ?? collect())
->mapWithKeys(fn($e) => [$e->job_id . ':' . $e->task_type_id => true]);
$procPausedCombos = ($pausedEntries ?? collect())
->filter(fn($e) => !isset($procRunningKeys[$e->job_id . ':' . $e->task_type_id]))
->unique(fn($e) => $e->job_id . ':' . $e->task_type_id)
->values();
@endphp
@if($procPausedCombos->count() > 0)
Paused Timers
Resume or complete these paused tasks.
@foreach($procPausedCombos as $pausedEntry)
@php
$totalPausedMins = \App\Models\TimeEntry::where('user_id', auth()->id())
->where('job_id', $pausedEntry->job_id)
->where('task_type_id', $pausedEntry->task_type_id)
->whereIn('status', ['paused', 'completed'])
->sum('duration_minutes');
$totalPausedHrs = round($totalPausedMins / 60, 2);
@endphp
{{ $pausedEntry->taskType?->name ?? '—' }}
{{ $pausedEntry->job?->job_number ?? '—' }}
@if($pausedEntry->job?->contact)· {{ $pausedEntry->job->contact->first_name }} {{ $pausedEntry->job->contact->last_name }}@endif
{{ $totalPausedHrs }}h logged · paused {{ $pausedEntry->stopped_at?->diffForHumans() }}
@endforeach
@endif
{{-- Processor: no running or paused timers --}}
@if(($runningEntries ?? collect())->count() === 0 && $procPausedCombos->count() === 0)
No active timers. Use the form above to start tracking time on a task.
@endif
@endif {{-- end $isProcessor --}}
{{-- ── Factory Employee: Assigned Tasks (Manufacture Tab) ── --}}
@if($isFactoryEmployee && isset($assignments) && $assignments->count() > 0)
@foreach($assignments->groupBy('job_id') as $jobId => $jobAssignments)
@php $jobObj = $jobAssignments->first()->job; @endphp
{{ $jobObj?->job_number ?? 'J-?' }}
{{ $jobObj?->contact?->first_name }} {{ $jobObj?->contact?->last_name }}
@foreach($jobAssignments as $assignment)
@php
$loggedMins = \App\Models\TimeEntry::where('user_id', $assignment->user_id)
->where('job_id', $assignment->job_id)
->where('task_type_id', $assignment->task_type_id)
->whereIn('status', ['paused','completed'])->sum('duration_minutes');
$runningEntryForTask = ($runningEntries ?? collect())
->where('job_id', $assignment->job_id)
->where('task_type_id', $assignment->task_type_id)->first();
$runningMins = $runningEntryForTask ? $runningEntryForTask->started_at->diffInMinutes(now()) : 0;
$loggedHrs = round(($loggedMins + $runningMins) / 60, 2);
$pct = ($assignment->allocated_hours > 0) ? min(100, round(($loggedHrs / $assignment->allocated_hours) * 100, 1)) : null;
$isRunningNow = $runningEntryForTask !== null;
$isPaused = isset($pausedEntries)
&& $pausedEntries->where('job_id', $assignment->job_id)->where('task_type_id', $assignment->task_type_id)->count() > 0
&& !$isRunningNow;
$statusLabel = $assignment->task_status ?? 'pending';
$statusBadge = match($statusLabel) {
'completed' => 'badge-green',
'in_progress' => 'badge-blue',
'paused' => 'badge-yellow',
default => 'badge-gray',
};
$timerKey = $assignment->job_id . ':' . $assignment->task_type_id;
@endphp
{{ $assignment->taskType?->name ?? '—' }}
{{ ucfirst(str_replace('_',' ', $statusLabel)) }}
@if($isRunningNow)
Running — {{ $runningEntryForTask->elapsed }}
@endif
@if($assignment->allocated_hours)
Allocated: {{ $assignment->allocated_hours }}h
@endif
@if($assignment->allocated_hours)
{{ $loggedHrs }}h logged{{ $isRunningNow ? ' (timer running)' : '' }}
{{ $pct !== null ? $pct.'%' : '—' }}
@else
{{ $loggedHrs }}h logged — no allocated hours set
@endif
@if($assignment->task_status !== 'completed')
@if(!$isRunningNow)
@else
@endif
@else
Task completed — hours included in gross profit report
@endif
@endforeach
@endforeach
@else
@if($isFactoryEmployee)
@endif
@endif
{{-- ── Paused timers (other non-employee, non-trades, non-processor roles) ── --}}
@if(!$isFactoryEmployee && !$isTrades && !$isProcessor && isset($pausedEntries) && $pausedEntries->count() > 0)
Paused Timers
These timers are paused. Resume to continue counting time.
@foreach($pausedEntries as $paused)
{{ $paused->taskType?->name ?? '—' }}
{{ $paused->job?->job_number ?? '—' }} · Paused {{ $paused->stopped_at?->diffForHumans() }}
{{ $paused->elapsed }} recorded so far
@endforeach
@endif
{{-- ── Invoice CTA (hourly roles that need invoice approval, excluding direct roles) ── --}}
@php $directRoles = ['factory_employee', 'processor', 'lead_installer', 'trades']; @endphp
@if(auth()->user()->hourly_rate && !in_array(auth()->user()->role?->name, $directRoles))
Ready to be invoiced?
Request invoice approval to notify admin & accounts for your time entries.
Request Invoice Approval
Select your pay period. Admin & accounts will be notified to generate and approve your invoice.
@endif
@endsection