Build live HickLab3D overview dashboard
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 24s
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 24s
This commit is contained in:
@@ -1,19 +1,121 @@
|
||||
@extends('layouts.app')
|
||||
@section('title', 'Overview')
|
||||
@section('content')
|
||||
<section class="flex flex-wrap items-center justify-between gap-6 overflow-hidden rounded-2xl border border-brand/20 bg-gradient-to-r from-brand/10 via-slate-900 to-slate-900 p-6 sm:p-8">
|
||||
<div class="max-w-2xl"><p class="mb-3 text-xs font-bold uppercase tracking-[.22em] text-brand">HickLab3D workshop</p><h2 class="text-2xl font-bold sm:text-3xl">Good to see you, {{ auth()->user()->username }}.</h2><p class="mt-2 text-slate-400">Sales, production, materials and dispatch in one place.</p></div>
|
||||
<div class="grid size-32 shrink-0 place-items-center rounded-2xl bg-[#d8dad8] p-2 ring-1 ring-white/15"><img src="{{ asset('hicklab3d-monogram.png') }}" alt="HickLab3D monogram" class="max-h-full max-w-full object-contain"></div>
|
||||
</section>
|
||||
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||
@if(auth()->user()->role === 'admin')<a href="{{ route('etsy.index') }}" class="rounded-2xl border border-slate-800 bg-slate-900 p-6 transition hover:border-brand/50"><p class="text-sm text-slate-400">Sales channel</p><h3 class="mt-5 text-2xl font-bold">Etsy shop</h3><p class="mt-4 text-sm text-brand">Orders and listings →</p></a>@endif
|
||||
<a href="{{ route('printers.index') }}" class="rounded-2xl border border-slate-800 bg-slate-900 p-6 transition hover:border-brand/50"><p class="text-sm text-slate-400">Production</p><h3 class="mt-5 text-2xl font-bold">BamBuddy</h3><p class="mt-4 text-sm text-brand">Live print farm →</p></a>
|
||||
<a href="{{ route('filaments.index') }}" class="rounded-2xl border border-slate-800 bg-slate-900 p-6 transition hover:border-brand/50"><p class="text-sm text-slate-400">Materials</p><h3 class="mt-5 text-2xl font-bold">{{ $filamentCount }} tracked</h3><p class="mt-4 text-sm text-brand">Filament prices →</p></a>
|
||||
@if(auth()->user()->role === 'admin')<a href="{{ route('shipping') }}" class="rounded-2xl border border-slate-800 bg-slate-900 p-6 transition hover:border-brand/50"><p class="text-sm text-slate-400">Dispatch</p><h3 class="mt-5 text-2xl font-bold">Parcel2Go</h3><p class="mt-4 text-sm text-brand">Quotes and shipments →</p></a>@endif
|
||||
<a href="{{ route('dryer') }}" class="rounded-2xl border border-slate-800 bg-slate-900 p-6 transition hover:border-brand/50"><p class="text-sm text-slate-400">Workshop tools</p><h3 class="mt-5 text-2xl font-bold">Filament dryer</h3><p class="mt-4 text-sm text-brand">Status and controls →</p></a>
|
||||
@if($userCount !== null)<a href="{{ route('users.index') }}" class="rounded-2xl border border-slate-800 bg-slate-900 p-6 transition hover:border-brand/50"><p class="text-sm text-slate-400">Team</p><h3 class="mt-5 text-2xl font-bold">{{ $userCount }} accounts</h3><p class="mt-4 text-sm text-brand">Manage access →</p></a>@endif
|
||||
<div class="flex flex-wrap items-end justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-xs font-bold uppercase tracking-[.2em] text-brand">Workshop overview</p>
|
||||
<h2 class="mt-2 text-2xl font-bold tracking-tight sm:text-3xl">Good to see you, {{ auth()->user()->username }}.</h2>
|
||||
<p class="mt-2 text-sm text-slate-400">What needs attention across the shop and workshop.</p>
|
||||
</div>
|
||||
<p class="rounded-full border border-slate-700 px-3 py-1.5 text-xs text-slate-400">Updated when you open this page</p>
|
||||
</div>
|
||||
<section class="rounded-2xl border border-slate-800 bg-slate-900 p-6"><div class="mb-5 flex items-center justify-between"><h2 class="text-lg font-semibold">Recently tracked filament</h2><a class="text-sm text-brand" href="{{ route('filaments.index') }}">View all →</a></div>
|
||||
<div class="divide-y divide-slate-800">@forelse($recentFilaments as $filament)<div class="flex items-center justify-between gap-4 py-4"><div><p class="font-medium">{{ $filament->filamentName }}</p><p class="text-sm text-slate-400">{{ $filament->brand ?: 'Unknown brand' }} · {{ $filament->material ?: 'Material pending' }}</p></div><span class="font-semibold">{{ $filament->prices->first() ? '£'.number_format($filament->prices->first()->price, 2) : 'Pending' }}</span></div>@empty<p class="py-4 text-sm text-slate-400">No filament is being tracked yet.</p>@endforelse</div>
|
||||
|
||||
<section class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4" aria-label="Key metrics">
|
||||
@if($isAdmin)
|
||||
<a href="{{ route('etsy.index') }}" class="group rounded-2xl border border-slate-800 bg-slate-900 p-5 transition hover:border-brand/50">
|
||||
<div class="flex items-center justify-between"><p class="text-sm font-medium text-slate-400">Awaiting dispatch</p><span class="text-brand">↗</span></div>
|
||||
<p class="mt-4 text-3xl font-bold tabular-nums">{{ $etsyConnected ? $awaitingDispatch : '—' }}</p>
|
||||
<p class="mt-2 truncate text-xs text-slate-500">{{ $etsyStatus }} · Latest 20 Etsy receipts</p>
|
||||
</a>
|
||||
@endif
|
||||
<a href="{{ route('printers.index') }}" class="group rounded-2xl border border-slate-800 bg-slate-900 p-5 transition hover:border-brand/50">
|
||||
<div class="flex items-center justify-between"><p class="text-sm font-medium text-slate-400">Printing now</p><span class="text-brand">↗</span></div>
|
||||
<p class="mt-4 text-3xl font-bold tabular-nums">{{ $printerStatus === 'Live from BamBuddy' ? $printingCount : '—' }}<span class="ml-2 text-base font-medium text-slate-500">/ {{ $printerStatus === 'Live from BamBuddy' ? count($printers) : '—' }}</span></p>
|
||||
<p class="mt-2 text-xs text-slate-500">{{ $printerStatus }}</p>
|
||||
</a>
|
||||
<a href="{{ route('filaments.index') }}" class="group rounded-2xl border border-slate-800 bg-slate-900 p-5 transition hover:border-brand/50">
|
||||
<div class="flex items-center justify-between"><p class="text-sm font-medium text-slate-400">Tracked materials</p><span class="text-brand">↗</span></div>
|
||||
<p class="mt-4 text-3xl font-bold tabular-nums">{{ $filamentCount }}</p>
|
||||
<p class="mt-2 text-xs text-slate-500">{{ count($materials) }} material types in your tracker</p>
|
||||
</a>
|
||||
@if($isAdmin)
|
||||
<a href="{{ route('etsy.index') }}" class="group rounded-2xl border border-slate-800 bg-slate-900 p-5 transition hover:border-brand/50">
|
||||
<div class="flex items-center justify-between"><p class="text-sm font-medium text-slate-400">Active listings</p><span class="text-brand">↗</span></div>
|
||||
<p class="mt-4 text-3xl font-bold tabular-nums">{{ $listingCount ?? '—' }}</p>
|
||||
<p class="mt-2 text-xs text-slate-500">{{ $etsyStatus }}</p>
|
||||
</a>
|
||||
@else
|
||||
<a href="{{ route('dryer') }}" class="group rounded-2xl border border-slate-800 bg-slate-900 p-5 transition hover:border-brand/50">
|
||||
<div class="flex items-center justify-between"><p class="text-sm font-medium text-slate-400">Filament dryer</p><span class="text-brand">↗</span></div>
|
||||
<p class="mt-4 text-2xl font-bold">{{ $dryerState }}</p>
|
||||
<p class="mt-2 text-xs text-slate-500">Current workshop state</p>
|
||||
</a>
|
||||
@endif
|
||||
</section>
|
||||
|
||||
<div class="grid gap-4 xl:grid-cols-3">
|
||||
@if($isAdmin)
|
||||
<section class="min-w-0 rounded-2xl border border-slate-800 bg-slate-900 xl:col-span-2">
|
||||
<div class="flex flex-wrap items-start justify-between gap-3 border-b border-slate-800 px-5 py-5 sm:px-6">
|
||||
<div><h3 class="font-semibold">Orders awaiting dispatch</h3><p class="mt-1 text-xs text-slate-400">Placed in the last 14 days among the latest 20 unshipped Etsy receipts</p></div>
|
||||
<a href="{{ route('etsy.index') }}" class="text-sm font-semibold text-brand hover:underline">View Etsy →</a>
|
||||
</div>
|
||||
@if($etsyConnected && array_sum($orderChart['series']) > 0)
|
||||
<div class="p-5 sm:p-6"><div class="h-64 w-full" data-dashboard-chart="orders" data-chart="{{ json_encode($orderChart) }}" role="img" aria-label="Etsy orders awaiting dispatch by placement date"></div></div>
|
||||
@else
|
||||
<div class="flex min-h-64 flex-col items-center justify-center gap-2 px-6 text-center"><p class="font-medium">No recent dispatch orders to chart</p><p class="max-w-sm text-sm text-slate-400">{{ $etsyStatus === 'Not connected' ? 'Connect Etsy to see the current dispatch queue.' : ($etsyStatus === 'Temporarily unavailable' ? 'Etsy data is temporarily unavailable.' : 'Paid orders awaiting dispatch will appear here.') }}</p></div>
|
||||
@endif
|
||||
</section>
|
||||
@endif
|
||||
<section class="min-w-0 rounded-2xl border border-slate-800 bg-slate-900 {{ $isAdmin ? '' : 'xl:col-span-2' }}">
|
||||
<div class="border-b border-slate-800 px-5 py-5 sm:px-6"><h3 class="font-semibold">Material mix</h3><p class="mt-1 text-xs text-slate-400">Tracked filaments by material</p></div>
|
||||
@if($filamentCount)
|
||||
<div class="p-5 sm:p-6"><div class="h-64 w-full" data-dashboard-chart="materials" data-chart="{{ json_encode($materials) }}" role="img" aria-label="Tracked filaments by material"></div></div>
|
||||
@else
|
||||
<div class="flex min-h-64 flex-col items-center justify-center gap-2 px-6 text-center"><p class="font-medium">No materials tracked yet</p><a href="{{ route('filaments.index') }}" class="text-sm text-brand hover:underline">Open filament tracker →</a></div>
|
||||
@endif
|
||||
</section>
|
||||
@unless($isAdmin)
|
||||
<section class="rounded-2xl border border-slate-800 bg-slate-900 p-5 sm:p-6"><h3 class="font-semibold">Workshop status</h3><p class="mt-4 text-sm text-slate-400">Print farm: {{ $printerStatus }}</p><p class="mt-2 text-sm text-slate-400">Dryer: {{ $dryerState }}</p></section>
|
||||
@endunless
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 xl:grid-cols-2">
|
||||
<section class="rounded-2xl border border-slate-800 bg-slate-900">
|
||||
<div class="flex items-center justify-between gap-3 border-b border-slate-800 px-5 py-4 sm:px-6"><div><h3 class="font-semibold">Print farm</h3><p class="mt-1 text-xs text-slate-400">{{ $printerStatus }}</p></div><a href="{{ route('printers.index') }}" class="text-sm font-semibold text-brand hover:underline">View all →</a></div>
|
||||
<div class="divide-y divide-slate-800 px-5 sm:px-6">
|
||||
@forelse(array_slice($printers, 0, 4) as $printer)
|
||||
@php $live = $printer['live'] ?? []; @endphp
|
||||
<div class="flex items-center justify-between gap-4 py-4"><div class="min-w-0"><p class="truncate font-medium">{{ $printer['name'] ?? 'Printer' }}</p><p class="mt-1 truncate text-xs text-slate-400">{{ $live['current_print'] ?? $live['subtask_name'] ?? 'No active job' }}</p></div><div class="text-right"><p @class(['text-sm font-semibold', 'text-brand' => in_array(strtoupper((string) ($live['state'] ?? '')), ['RUNNING', 'PRINTING']), 'text-slate-400' => ! in_array(strtoupper((string) ($live['state'] ?? '')), ['RUNNING', 'PRINTING'])])>{{ $live['state'] ?? $printer['status'] ?? 'Unavailable' }}</p><p class="mt-1 text-xs text-slate-500">{{ isset($live['progress']) ? number_format((float) $live['progress'], 0).'%' : '—' }}</p></div></div>
|
||||
@empty
|
||||
<p class="py-8 text-sm text-slate-400">{{ $printerStatus === 'Live from BamBuddy' ? 'No printers returned by BamBuddy.' : $printerStatus }}</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</section>
|
||||
@if($isAdmin)
|
||||
<section class="rounded-2xl border border-slate-800 bg-slate-900">
|
||||
<div class="flex items-center justify-between gap-3 border-b border-slate-800 px-5 py-4 sm:px-6"><div><h3 class="font-semibold">Orders to pack</h3><p class="mt-1 text-xs text-slate-400">Latest paid Etsy receipts awaiting dispatch</p></div><a href="{{ route('etsy.index') }}" class="text-sm font-semibold text-brand hover:underline">View all →</a></div>
|
||||
<div class="divide-y divide-slate-800 px-5 sm:px-6">
|
||||
@forelse($receipts as $receipt)
|
||||
<div class="flex items-center justify-between gap-4 py-4"><div class="min-w-0"><p class="truncate font-medium">#{{ $receipt['receipt_id'] ?? '—' }} · {{ $receipt['name'] ?? 'Buyer' }}</p><p class="mt-1 text-xs text-slate-400">{{ isset($receipt['create_timestamp']) ? \Illuminate\Support\Carbon::createFromTimestamp((int) $receipt['create_timestamp'])->format('d M Y') : 'Date unavailable' }}</p></div><span class="shrink-0 rounded-full bg-amber-400/10 px-2.5 py-1 text-xs font-medium text-amber-200">Pack & send</span></div>
|
||||
@empty
|
||||
<p class="py-8 text-sm text-slate-400">{{ $etsyStatus === 'Not connected' ? 'Connect Etsy to see orders.' : ($etsyStatus === 'Temporarily unavailable' ? 'Etsy data is temporarily unavailable.' : 'No orders awaiting dispatch.') }}</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 xl:grid-cols-2">
|
||||
@if($isAdmin)
|
||||
<section class="rounded-2xl border border-slate-800 bg-slate-900">
|
||||
<div class="flex items-center justify-between gap-3 border-b border-slate-800 px-5 py-4 sm:px-6"><div><h3 class="font-semibold">Recent shipments</h3><p class="mt-1 text-xs text-slate-400">Parcel2Go · {{ $shippingStatus }}</p></div><a href="{{ route('shipping') }}" class="text-sm font-semibold text-brand hover:underline">Labels & collections →</a></div>
|
||||
<div class="divide-y divide-slate-800 px-5 sm:px-6">
|
||||
@forelse($shipments as $shipment)
|
||||
<div class="flex items-center justify-between gap-4 py-4"><div class="min-w-0"><p class="truncate font-medium">#{{ $shipment['OrderLineId'] ?? '—' }} · {{ $shipment['Courier'] ?? 'Courier' }}</p><p class="mt-1 truncate text-xs text-slate-400">{{ $shipment['DeliveryAddress']['ContactName'] ?? 'Recipient unavailable' }}</p></div><span class="shrink-0 text-xs text-slate-400">{{ ($shipment['Tracking']['Delivered'] ?? false) ? 'Delivered' : 'In transit' }}</span></div>
|
||||
@empty<p class="py-8 text-sm text-slate-400">{{ $shippingStatus === 'Live account' ? 'No recent shipments.' : $shippingStatus }}</p>@endforelse
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
<section class="rounded-2xl border border-slate-800 bg-slate-900">
|
||||
<div class="flex items-center justify-between gap-3 border-b border-slate-800 px-5 py-4 sm:px-6"><div><h3 class="font-semibold">Filament tracker</h3><p class="mt-1 text-xs text-slate-400">Most recently tracked materials</p></div><a href="{{ route('filaments.index') }}" class="text-sm font-semibold text-brand hover:underline">View all →</a></div>
|
||||
<div class="divide-y divide-slate-800 px-5 sm:px-6">
|
||||
@forelse($recentFilaments as $filament)
|
||||
<div class="flex items-center justify-between gap-4 py-4"><div class="min-w-0"><p class="truncate font-medium">{{ $filament->filamentName }}</p><p class="mt-1 text-xs text-slate-400">{{ $filament->brand ?: 'Unknown brand' }} · {{ $filament->material ?: 'Material pending' }}</p></div><span class="shrink-0 font-semibold tabular-nums">{{ $filament->prices->first() ? '£'.number_format($filament->prices->first()->price, 2) : 'Pending' }}</span></div>
|
||||
@empty<p class="py-8 text-sm text-slate-400">No filament is being tracked yet.</p>@endforelse
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="flex flex-wrap items-center justify-between gap-4 rounded-2xl border border-slate-800 bg-slate-900 px-5 py-4 sm:px-6"><div><h3 class="font-semibold">Dryer status</h3><p class="mt-1 text-sm text-slate-400">{{ $dryerState }}</p></div><a href="{{ route('dryer') }}" class="text-sm font-semibold text-brand hover:underline">Open controls →</a></section>
|
||||
@endsection
|
||||
|
||||
Reference in New Issue
Block a user