@php use Illuminate\Support\Carbon; // Labels come from Job::STATUS_LABELS so the board can never disagree with // the model about what a status is called. $statusMeta = [ 'pending' => ['label' => App\Models\Job::statusLabel('pending'), 'color' => 'slate', 'dot' => 'bg-slate-400'], 'in_production' => ['label' => App\Models\Job::statusLabel('in_production'), 'color' => 'sky', 'dot' => 'bg-sky-500'], 'on_hold' => ['label' => App\Models\Job::statusLabel('on_hold'), 'color' => 'amber', 'dot' => 'bg-amber-500'], 'completed' => ['label' => App\Models\Job::statusLabel('completed'), 'color' => 'emerald', 'dot' => 'bg-emerald-500'], 'ready_for_collection' => ['label' => App\Models\Job::statusLabel('ready_for_collection'), 'color' => 'amber', 'dot' => 'bg-amber-400'], 'delivered' => ['label' => App\Models\Job::statusLabel('delivered'), 'color' => 'violet', 'dot' => 'bg-violet-500'], 'closed' => ['label' => App\Models\Job::statusLabel('closed'), 'color' => 'slate', 'dot' => 'bg-slate-500'], 'cancelled' => ['label' => App\Models\Job::statusLabel('cancelled'), 'color' => 'rose', 'dot' => 'bg-rose-400'], ]; // The counter-facing state: a job sitting here is finished work waiting on // the customer, and the board has to say so at a glance. $isReady = fn ($job) => $job->status === 'ready_for_collection'; // Commercial state (spec §2), the funding dimension, separate from status. $commercialMeta = [ 'not_funded' => ['label' => __('Not Funded'), 'color' => 'slate'], 'deposit_paid' => ['label' => __('Deposit Paid'), 'color' => 'amber'], 'fully_paid' => ['label' => __('Fully Paid'), 'color' => 'emerald'], ]; // Categories carry no colour of their own, derive a stable accent so the // board reads like the reference (each category keeps one consistent hue). $categoryPalette = ['indigo', 'sky', 'violet', 'emerald', 'amber', 'rose']; $categoryColor = function ($category) use ($categoryPalette) { if (! $category) { return 'slate'; } return $categoryPalette[$category->id % count($categoryPalette)]; }; // Kanban progress bar takes the column's status colour (solid). $kanbanBar = fn ($job) => [ 'pending' => 'bg-slate-300', 'in_production' => 'bg-sky-500', 'on_hold' => 'bg-amber-500', 'completed' => 'bg-emerald-500', 'ready_for_collection' => 'bg-amber-400', 'delivered' => 'bg-emerald-500', 'closed' => 'bg-slate-500', ][$job->status] ?? 'bg-slate-300'; // Relative due-date pill text + colour. Once the work is made the due date // stops being a countdown and the cell just states where the job is — // except at the counter, where "waiting for the customer" is the point. $dueMeta = function ($job) use ($statusMeta) { if ($job->status === 'ready_for_collection') { return ['label' => $statusMeta[$job->status]['label'], 'cls' => 'text-amber-600 font-medium']; } if (in_array($job->status, ['completed', 'delivered', 'closed'], true)) { return ['label' => $statusMeta[$job->status]['label'], 'cls' => 'text-emerald-600']; } if (! $job->due_date) { return ['label' => '—', 'cls' => 'text-slate-400']; } $days = (int) Carbon::today()->diffInDays($job->due_date->copy()->startOfDay(), false); if ($days < 0) { return ['label' => __(':n days overdue', ['n' => abs($days)]), 'cls' => 'text-rose-600 font-medium']; } if ($days === 0) { return ['label' => __('Due today'), 'cls' => 'text-rose-600 font-medium']; } if ($days === 1) { return ['label' => __('1 day left'), 'cls' => 'text-rose-500']; } if ($days <= 3) { return ['label' => __(':n days left', ['n' => $days]), 'cls' => 'text-amber-600']; } return ['label' => __(':n days left', ['n' => $days]), 'cls' => 'text-slate-400']; }; $progressBar = fn ($job) => in_array($job->status, ['completed', 'delivered', 'closed'], true) ? 'bg-emerald-500' : ($job->status === 'ready_for_collection' ? 'bg-amber-400' : 'bg-gradient-to-r from-indigo-500 to-violet-500'); $sparkStroke = [ 'indigo' => '#124a61', 'sky' => '#0ea5e9', 'amber' => '#f59e0b', 'emerald' => '#10b981', 'violet' => '#8b5cf6', 'slate' => '#94a3b8', ]; @endphp
{{ $stat['label'] }}
{{ $stat['count'] }}
{{ $stat['caption'] }}
{{ $productionPeriodLabel }} · {{ $production['jobs_completed'] }} {{ __('completed') }}
{{ $tile['label'] }}
{{ $tile['value'] }}
{{ $tile['note'] }}
{{ __('Move to') }}
@forelse ($job->allowedTransitions() as $target) @empty{{ __('This job is finished.') }}
@endforelse @if ($canDelete) @endif{{ $job->customer?->name ?: __('No customer') }}
@if ($job->due_date)
{{ __('No jobs') }}
@endforelse{{ __(':count selected', ['count' => count($selected)]) }}
| pluck('id')->values()) : [])" @checked(count($selected) > 0 && count($selected) >= $jobs->count()) class="rounded border-slate-300 text-indigo-600 focus:ring-indigo-500" /> | {{ __('Title / Customer') }} | {{ __('Category') }} | {{ __('Value') }} | {{ __('Actions') }} | ||||
|---|---|---|---|---|---|---|---|---|
|
{{ $job->job_number }}
#{{ $job->id }} |
{{ $job->title }} {{ $job->customer?->name ?: __('No customer') }} |
|
@if ($job->category)
|
{{ $job->due_date?->format('Y-m-d') ?: '—' }} {{ $due['label'] }} |
@if ($job->value !== null) {{ $job->value_currency }} {{ number_format($job->value, 2) }} @if ((float) ($job->invoice?->refunded_total ?? 0) > 0) − {{ $job->value_currency }} {{ number_format($job->invoice->refunded_total, 2) }} {{ __('refunded') }} @endif @else — @endif |
|
|
|
|
|
||||||||
{{ $job->customer?->name ?: '—' }}
{{ $due['label'] }}
{{ __('A job is created from a sale, pick the invoice it fulfils.') }}
{{ __('This invoice has no line items to build a job from.') }}
@else{{ __('Tick which product(s) this job is for.') }}
{{ __('Completed and delivered jobs are always shown at 100%.') }}
{{ __('Record the outsourced portion as a cost line linked to a supplier.') }}
{{ __('Once the job is saved, open its Products tab to mark which products go out and to which partner.') }}
@endif @if ($fulfilment === 'outsourced'){{ __('No partners yet.') }} {{ __('Add one') }}.
@else{{ __('The company this job is outsourced to.') }}
@endif