Add project listing selector, confirmed revision and private 3MF renders

This commit is contained in:
2026-11-08 18:49:32 +00:00
parent e62b6800a6
commit 4e675510fd
29 changed files with 3394 additions and 14 deletions
+64 -8
View File
@@ -16,25 +16,54 @@
</div>
@if($project->notes)<p class="rounded-2xl border border-slate-800 bg-slate-900 p-4 text-sm text-slate-300">{{ $project->notes }}</p>@endif
<div class="rounded-2xl border border-slate-800 bg-slate-900 p-4">
@if($project->currentRevision)
<div class="flex flex-wrap items-center justify-between gap-3">
<p class="text-sm text-slate-300">Confirmed current revision: <span class="rounded-full bg-brand/10 px-2 py-1 text-xs font-semibold text-brand">Rev {{ $project->currentRevision->revision_number }}</span></p>
<form method="post" action="{{ route('projects.current-revision.clear', $project) }}">@csrf @method('DELETE')<input type="hidden" name="lock_version" value="{{ $project->lock_version }}"><button class="rounded-lg border border-rose-500/40 px-3 py-2 text-xs text-rose-300">Clear current revision</button></form>
</div>
@else
<p class="text-sm text-slate-400">No revision has been confirmed as current yet.</p>
@endif
</div>
<section>
<h3 class="text-lg font-semibold">Revisions &amp; files</h3>
<div class="mt-3 grid gap-3">
@forelse($project->revisions as $revision)
<article class="rounded-2xl border border-slate-800 bg-slate-900 p-5">
<div class="flex flex-wrap items-center justify-between gap-3">
<p class="font-semibold">Revision {{ $revision->revision_number }} <span class="ml-2 rounded-full bg-slate-800 px-2 py-1 text-xs capitalize">{{ $revision->status }}</span></p>
<p class="font-semibold">Revision {{ $revision->revision_number }} <span class="ml-2 rounded-full bg-slate-800 px-2 py-1 text-xs capitalize">{{ $revision->status }}</span>@if($project->current_revision_id === $revision->id)<span class="ml-2 rounded-full bg-brand/10 px-2 py-1 text-xs text-brand">Current</span>@endif</p>
@if(! $revision->isApproved())
<form method="post" action="{{ route('projects.revisions.approve', [$project, $revision]) }}"><input type="hidden">@csrf<button class="rounded-lg border border-brand/40 px-3 py-2 text-xs text-brand">Approve</button></form>
@else
<span class="text-xs text-slate-400">Approved by {{ $revision->approver?->username }} on {{ $revision->approved_at?->format('d M Y') }}</span>
<div class="flex items-center gap-3">
<span class="text-xs text-slate-400">Approved by {{ $revision->approver?->username }} on {{ $revision->approved_at?->format('d M Y') }}</span>
@if($project->current_revision_id !== $revision->id)
<form method="post" action="{{ route('projects.revisions.confirm-current', [$project, $revision]) }}">@csrf<input type="hidden" name="lock_version" value="{{ $project->lock_version }}"><button class="rounded-lg border border-brand/40 px-3 py-2 text-xs text-brand">Confirm as current</button></form>
@endif
</div>
@endif
</div>
@if($revision->change_notes)<p class="mt-2 text-sm text-slate-400">{{ $revision->change_notes }}</p>@endif
<ul class="mt-3 divide-y divide-slate-800 text-sm">
@foreach($revision->files as $file)
<li class="flex items-center justify-between gap-3 py-2">
<span><span class="rounded-full bg-slate-800 px-2 py-0.5 text-xs uppercase">{{ $file->extension }}</span> {{ $file->original_filename }} <span class="font-mono text-xs text-slate-500">({{ number_format($file->byte_size / 1024, 1) }} KB)</span></span>
<a class="text-brand hover:underline" href="{{ route('projects.files.download', [$project, $file]) }}">Download</a>
<li class="py-2">
<div class="flex items-center justify-between gap-3">
<span><span class="rounded-full bg-slate-800 px-2 py-0.5 text-xs uppercase">{{ $file->extension }}</span> {{ $file->original_filename }} <span class="font-mono text-xs text-slate-500">({{ number_format($file->byte_size / 1024, 1) }} KB)</span></span>
<a class="text-brand hover:underline" href="{{ route('projects.files.download', [$project, $file]) }}">Download</a>
</div>
@if($file->extension === '3mf')
<div class="mt-2 flex flex-wrap items-center gap-3">
@if($file->render)
<img src="{{ route('projects.files.render.preview', [$project, $file]) }}" alt="{{ $file->render->alt_text }}" class="h-16 w-16 rounded-lg border border-slate-700 bg-slate-950 object-contain">
<a class="text-xs text-brand hover:underline" href="{{ route('projects.files.render.download', [$project, $file]) }}">Download product image</a>
<span class="text-xs text-slate-500">{{ $file->render->triangle_count }} triangles · {{ $file->render->instance_count }} instance(s) · {{ strtoupper($file->render->renderer) }}</span>
@else
<form method="post" action="{{ route('projects.files.render', [$project, $file]) }}">@csrf<button class="rounded-lg border border-brand/40 px-3 py-2 text-xs text-brand">Generate product image</button></form>
@endif
</div>
@endif
</li>
@endforeach
</ul>
@@ -57,7 +86,7 @@
</details>
</section>
<section>
<section id="listings">
<h3 class="text-lg font-semibold">Listings</h3>
<div class="mt-3 grid gap-3">
@forelse($project->listingLinks as $link)
@@ -96,13 +125,40 @@
<p class="text-sm text-slate-400">No listings linked yet.</p>
@endforelse
</div>
<details class="mt-4 rounded-2xl border border-slate-800 bg-slate-900 p-5">
<details class="mt-4 rounded-2xl border border-slate-800 bg-slate-900 p-5" open>
<summary class="cursor-pointer text-sm font-semibold text-brand">Link an Etsy listing</summary>
@if(! $listingOptionsConnected)
<p class="mt-3 rounded-xl border border-amber-500/30 bg-amber-500/10 p-4 text-sm text-amber-200">Connect the Etsy shop on the <a class="underline" href="{{ route('etsy.index') }}">Sales page</a> before linking a listing.</p>
@elseif($listingOptionsError)
<p class="mt-3 rounded-xl border border-rose-500/30 bg-rose-500/10 p-4 text-sm text-rose-200">{{ $listingOptionsError }}</p>
@else
@php($totalListingOptions = count($listingOptionsByState['active'] ?? []) + count($listingOptionsByState['draft'] ?? []))
<form method="post" action="{{ route('projects.listings.store', $project) }}" class="mt-4 flex flex-wrap gap-3">
@csrf
<input type="number" name="listing_id" placeholder="Etsy listing ID" required class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100">
@if($totalListingOptions === 0)
<p class="text-sm text-slate-400">No live or draft listings found in this shop.</p>
@else
<select name="listing_id" required class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3 text-slate-100">
<option value="">Select a listing… ({{ $totalListingOptions }} total)</option>
@if(count($listingOptionsByState['active'] ?? []) > 0)
<optgroup label="Live">
@foreach($listingOptionsByState['active'] as $option)
<option value="{{ $option['listing_id'] }}">{{ $option['title'] ?? 'Untitled listing' }} (#{{ $option['listing_id'] }}) · Live</option>
@endforeach
</optgroup>
@endif
@if(count($listingOptionsByState['draft'] ?? []) > 0)
<optgroup label="Draft">
@foreach($listingOptionsByState['draft'] as $option)
<option value="{{ $option['listing_id'] }}">{{ $option['title'] ?? 'Untitled listing' }} (#{{ $option['listing_id'] }}) · Draft</option>
@endforeach
</optgroup>
@endif
</select>
<button class="rounded-lg bg-brand px-4 py-2 text-sm font-semibold text-slate-950">Verify &amp; link</button>
@endif
</form>
@endif
</details>
</section>