@extends('layouts.app') @section('title', 'My Timesheet') @section('breadcrumb')Timesheet@endsection @section('content') @php $isFactoryEmployee = auth()->user()->role?->name === 'factory_employee'; $isTrades = auth()->user()->role?->name === 'trades'; $isProcessor = auth()->user()->role?->name === 'processor'; @endphp
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
@csrf
@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
@csrf

Please select a task type.

Please select a job.

@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)
@csrf
@elseif($isPausedNow)
@csrf
@else @if($isinvoicedCompleted?->status != 'completed')
@csrf
@endif @endif @if($hasInProgress)
@csrf
@endif @if($hasUninvoiced && !$hasInProgress)
@csrf
@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
@csrf
@error('task_type_id')

{{ $message }}

@enderror
@error('job_id')

{{ $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 }}
@csrf
@csrf
@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() }}
@csrf
@csrf
@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)

My Assigned Tasks

Job-wise task overview with progress @if(!$statuscompleted) Previous Completed Tasks @endif @if($statuscompleted) My Tasks @endif
@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)
@csrf
@else
@csrf
@endif
@csrf
@else
Task completed — hours included in gross profit report
@endif
@endforeach
@endforeach
@else @if($isFactoryEmployee)
No assigned tasks at the moment. Previous Completed Tasks
@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
@csrf
@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.
My Invoices
@endif
@endsection