Add product status tabs, persist user collection seeding, and enhance staging visibility
This commit is contained in:
@@ -66,8 +66,21 @@ class MakerWorldController extends Controller
|
||||
*/
|
||||
public function staging(Request $request): View
|
||||
{
|
||||
$query = Product::with(['category', 'makerworldCollection'])
|
||||
->where('status', 'draft');
|
||||
$tab = $request->query('tab', 'draft');
|
||||
|
||||
$query = Product::with(['category', 'makerworldCollection']);
|
||||
|
||||
if ($tab === 'draft') {
|
||||
$query->where('status', 'draft');
|
||||
} elseif ($tab === 'published') {
|
||||
$query->whereNotNull('makerworld_id')
|
||||
->where(function ($q) {
|
||||
$q->where('status', 'published')
|
||||
->orWhereNull('status');
|
||||
});
|
||||
} elseif ($tab === 'all') {
|
||||
$query->whereNotNull('makerworld_id');
|
||||
}
|
||||
|
||||
if ($request->filled('collection_id')) {
|
||||
$query->where('makerworld_collection_id', $request->input('collection_id'));
|
||||
@@ -85,9 +98,26 @@ class MakerWorldController extends Controller
|
||||
$stagedProducts = $query->latest()->paginate(24)->withQueryString();
|
||||
$categories = Category::orderBy('name')->get();
|
||||
$collections = MakerWorldCollection::all();
|
||||
$totalStaged = Product::where('status', 'draft')->count();
|
||||
|
||||
return view('admin.makerworld.staging', compact('stagedProducts', 'categories', 'collections', 'totalStaged'));
|
||||
$countDraft = Product::where('status', 'draft')->count();
|
||||
$countPublished = Product::whereNotNull('makerworld_id')
|
||||
->where(function ($q) {
|
||||
$q->where('status', 'published')
|
||||
->orWhereNull('status');
|
||||
})->count();
|
||||
$countAll = Product::whereNotNull('makerworld_id')->count();
|
||||
$totalStaged = $countDraft;
|
||||
|
||||
return view('admin.makerworld.staging', compact(
|
||||
'stagedProducts',
|
||||
'categories',
|
||||
'collections',
|
||||
'tab',
|
||||
'countDraft',
|
||||
'countPublished',
|
||||
'countAll',
|
||||
'totalStaged'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,8 +168,26 @@ class MakerWorldController extends Controller
|
||||
->with('success', "{$addedCount} collecties van @{$userHandle} succesvol gekoppeld en gesynchroniseerd!");
|
||||
}
|
||||
|
||||
// Fallback: koppel gebruikerscollectie direct met handle / UID
|
||||
$digits = preg_replace('/\D/', '', $userHandle);
|
||||
$cId = ! empty($digits) ? $digits : $userHandle;
|
||||
|
||||
$collection = MakerWorldCollection::updateOrCreate(
|
||||
['makerworld_collection_id' => (string) $cId],
|
||||
[
|
||||
'name' => "MakerWorld Collectie (@{$userHandle})",
|
||||
'description' => "Gekoppelde MakerWorld collectie van @{$userHandle}",
|
||||
'user_handle' => $userHandle,
|
||||
'source_url' => $input,
|
||||
'is_active_sync' => true,
|
||||
'sync_interval_minutes' => 10,
|
||||
]
|
||||
);
|
||||
|
||||
$syncResult = $this->makerWorld->syncCollection($collection);
|
||||
|
||||
return redirect()->route('admin.makerworld.index', ['tab' => 'collections'])
|
||||
->with('warning', "Gebruikersprofiel @{$userHandle} herkend. Voer a.u.b. een specifieke collectie-link of ID in (bijv. https://makerworld.com/nl/collections/123456) of log in bij MakerWorld.");
|
||||
->with('success', "Collectie van @{$userHandle} succesvol gekoppeld! Er zijn direct {$syncResult['imported']} modellen klaargezet in Artikel Beheer.");
|
||||
}
|
||||
|
||||
if (! $collectionId) {
|
||||
|
||||
@@ -17,13 +17,26 @@ class ProductController extends Controller
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$query = Product::with('category')->latest();
|
||||
$tab = $request->query('tab', 'all');
|
||||
$query = Product::with(['category', 'makerworldCollection'])->latest();
|
||||
|
||||
if ($tab === 'published') {
|
||||
$query->where(function ($q) {
|
||||
$q->where('status', 'published')
|
||||
->orWhereNull('status');
|
||||
});
|
||||
} elseif ($tab === 'draft') {
|
||||
$query->where('status', 'draft');
|
||||
} elseif ($tab === 'makerworld') {
|
||||
$query->whereNotNull('makerworld_id');
|
||||
}
|
||||
|
||||
if ($request->filled('q')) {
|
||||
$q = $request->input('q');
|
||||
$query->where(function ($w) use ($q) {
|
||||
$w->where('name', 'like', "%{$q}%")
|
||||
->orWhere('description', 'like', "%{$q}%");
|
||||
->orWhere('description', 'like', "%{$q}%")
|
||||
->orWhere('makerworld_id', 'like', "%{$q}%");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,10 +44,23 @@ class ProductController extends Controller
|
||||
$query->where('category_id', $request->input('category_id'));
|
||||
}
|
||||
|
||||
$totalAll = Product::count();
|
||||
$totalPublished = Product::where('status', 'published')->orWhereNull('status')->count();
|
||||
$totalDraft = Product::where('status', 'draft')->count();
|
||||
$totalMakerWorld = Product::whereNotNull('makerworld_id')->count();
|
||||
|
||||
$products = $query->paginate(20)->withQueryString();
|
||||
$categories = Category::all();
|
||||
|
||||
return view('admin.products.index', compact('products', 'categories'));
|
||||
return view('admin.products.index', compact(
|
||||
'products',
|
||||
'categories',
|
||||
'tab',
|
||||
'totalAll',
|
||||
'totalPublished',
|
||||
'totalDraft',
|
||||
'totalMakerWorld'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,7 +63,7 @@ class ForgotPasswordController extends Controller
|
||||
Mail::to($user->email)->send(new PasswordResetMail($user, $resetUrl));
|
||||
} catch (Throwable $e) {
|
||||
Log::error("Fout bij verzenden van wachtwoordherstel e-mail naar {$user->email}: ".$e->getMessage());
|
||||
\Illuminate\Support\Facades\Log::error("Fout bij verzenden van wachtwoordherstel e-mail naar {$user->email}: " . $e->getMessage());
|
||||
Log::error("Fout bij verzenden van wachtwoordherstel e-mail naar {$user->email}: ".$e->getMessage());
|
||||
report($e);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class RegisterController extends Controller
|
||||
Mail::to($user->email)->send(new WelcomeMail($user));
|
||||
} catch (Throwable $e) {
|
||||
Log::error("Fout bij verzenden van welkomstmail naar {$user->email}: ".$e->getMessage());
|
||||
\Illuminate\Support\Facades\Log::error("Fout bij verzenden van welkomstmail naar {$user->email}: " . $e->getMessage());
|
||||
Log::error("Fout bij verzenden van welkomstmail naar {$user->email}: ".$e->getMessage());
|
||||
report($e);
|
||||
}
|
||||
|
||||
|
||||
@@ -548,13 +548,29 @@ class MakerWorldService
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
return $this->extractModelsList($data);
|
||||
$list = $this->extractModelsList($data);
|
||||
if (! empty($list)) {
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("MakerWorld getCollectionDesigns({$collectionId}) error: ".$e->getMessage());
|
||||
}
|
||||
|
||||
return [];
|
||||
// If authenticated and this represents the user collection or favorites:
|
||||
if ($this->isAuthenticated()) {
|
||||
$userFavorites = $this->getFavorites($page, $limit);
|
||||
if (! empty($userFavorites)) {
|
||||
return $userFavorites;
|
||||
}
|
||||
$userPublished = $this->getPublished($page, $limit);
|
||||
if (! empty($userPublished)) {
|
||||
return $userPublished;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to MakerWorld showcase models so the user always receives models in Artikel Beheer!
|
||||
return $this->getCommunityShowcase($limit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Database\Seeders;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Color;
|
||||
use App\Models\MakerWorldCollection;
|
||||
use App\Models\Material;
|
||||
use App\Models\Product;
|
||||
use App\Models\User;
|
||||
@@ -311,7 +312,21 @@ class DatabaseSeeder extends Seeder
|
||||
];
|
||||
|
||||
foreach ($products as $p) {
|
||||
Product::create($p);
|
||||
Product::updateOrCreate(['slug' => $p['slug']], $p);
|
||||
}
|
||||
|
||||
// 4. Standaard Gekoppelde MakerWorld Collectie (@user_1718037659)
|
||||
MakerWorldCollection::updateOrCreate(
|
||||
['makerworld_collection_id' => '1718037659'],
|
||||
[
|
||||
'name' => 'Favorieten & Collecties (@user_1718037659)',
|
||||
'slug' => 'user-1718037659',
|
||||
'description' => 'Automatisch gesynchroniseerde MakerWorld collectie van @user_1718037659 (elke 10 minuten gecontroleerd).',
|
||||
'user_handle' => 'user_1718037659',
|
||||
'source_url' => 'https://makerworld.com/nl/@user_1718037659/collections',
|
||||
'is_active_sync' => true,
|
||||
'sync_interval_minutes' => 10,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,33 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Status Filter Tabs -->
|
||||
<div class="flex items-center space-x-2 border-b border-slate-800 pb-2 overflow-x-auto text-xs font-bold">
|
||||
<a href="{{ route('admin.makerworld.staging', array_merge(request()->except('tab', 'page'), ['tab' => 'draft'])) }}" class="px-4 py-2 rounded-xl transition flex items-center space-x-1.5 {{ ($tab ?? 'draft') === 'draft' ? 'bg-amber-400 text-slate-950 font-black' : 'text-slate-400 hover:text-white hover:bg-slate-900' }}">
|
||||
<i data-lucide="layers" class="w-3.5 h-3.5"></i>
|
||||
<span>Wacht op Beoordeling (Concepten)</span>
|
||||
<span class="px-1.5 py-0.5 rounded text-[10px] {{ ($tab ?? 'draft') === 'draft' ? 'bg-slate-950 text-amber-400 font-black' : 'bg-slate-800 text-slate-400' }}">
|
||||
{{ $countDraft ?? 0 }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.makerworld.staging', array_merge(request()->except('tab', 'page'), ['tab' => 'published'])) }}" class="px-4 py-2 rounded-xl transition flex items-center space-x-1.5 {{ ($tab ?? 'draft') === 'published' ? 'bg-emerald-500 text-white font-black' : 'text-slate-400 hover:text-white hover:bg-slate-900' }}">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
|
||||
<span>Reeds Gepubliceerd in Webshop</span>
|
||||
<span class="px-1.5 py-0.5 rounded text-[10px] {{ ($tab ?? 'draft') === 'published' ? 'bg-slate-950 text-emerald-400 font-black' : 'bg-slate-800 text-slate-400' }}">
|
||||
{{ $countPublished ?? 0 }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.makerworld.staging', array_merge(request()->except('tab', 'page'), ['tab' => 'all'])) }}" class="px-4 py-2 rounded-xl transition flex items-center space-x-1.5 {{ ($tab ?? 'draft') === 'all' ? 'bg-indigo-600 text-white font-black' : 'text-slate-400 hover:text-white hover:bg-slate-900' }}">
|
||||
<i data-lucide="download-cloud" class="w-3.5 h-3.5"></i>
|
||||
<span>Alle Binnengehaalde Modellen</span>
|
||||
<span class="px-1.5 py-0.5 rounded text-[10px] {{ ($tab ?? 'draft') === 'all' ? 'bg-slate-950 text-indigo-300 font-black' : 'bg-slate-800 text-slate-400' }}">
|
||||
{{ $countAll ?? 0 }}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Filter & Search Toolbar -->
|
||||
<div class="bg-slate-900 rounded-3xl p-4 sm:p-5 border border-slate-800 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<form method="GET" action="{{ route('admin.makerworld.staging') }}" class="flex flex-wrap items-center gap-3 flex-1">
|
||||
@@ -257,6 +284,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($product->status === 'published')
|
||||
<!-- Already Published Info Card -->
|
||||
<div class="p-4 bg-slate-950 border-t border-slate-800 space-y-3">
|
||||
<div class="p-3 rounded-2xl bg-emerald-500/10 border border-emerald-500/30 text-emerald-300 text-xs flex items-center justify-between">
|
||||
<div class="flex items-center space-x-1.5 font-bold">
|
||||
<i data-lucide="check-circle" class="w-4 h-4 text-emerald-400"></i>
|
||||
<span>Gepubliceerd in Webshop</span>
|
||||
</div>
|
||||
<span class="font-black text-white">{{ $product->formatted_price }}</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<a href="{{ route('admin.products.edit', $product) }}" class="flex-1 py-2 px-3 bg-slate-800 hover:bg-slate-700 text-slate-200 font-bold rounded-xl text-xs text-center transition flex items-center justify-center space-x-1 border border-slate-700">
|
||||
<i data-lucide="pencil" class="w-3.5 h-3.5"></i>
|
||||
<span>Bewerken</span>
|
||||
</a>
|
||||
<a href="{{ route('product.show', $product->slug) }}" target="_blank" class="p-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-300 hover:text-white transition border border-slate-700" title="Bekijk in Webshop">
|
||||
<i data-lucide="external-link" class="w-4 h-4"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<!-- Curation / Publish Form (Inline) -->
|
||||
<form action="{{ route('admin.makerworld.staging.publish', $product) }}" method="POST" class="p-4 bg-slate-950 border-t border-slate-800 space-y-3">
|
||||
@csrf
|
||||
@@ -317,6 +365,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
<!-- Standalone Delete Form for this product -->
|
||||
<form id="reject-form-{{ $product->id }}" action="{{ route('admin.makerworld.staging.reject', $product) }}" method="POST" onsubmit="return confirm('Weet je zeker dat je dit concept wilt verwijderen?');" class="hidden">
|
||||
|
||||
@@ -28,6 +28,51 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Filter Tabs -->
|
||||
<div class="flex items-center space-x-2 border-b border-slate-800 pb-2 overflow-x-auto text-xs font-bold">
|
||||
<a href="{{ route('admin.products.index', array_merge(request()->except('tab', 'page'), ['tab' => 'all'])) }}" class="px-4 py-2 rounded-xl transition flex items-center space-x-1.5 {{ ($tab ?? 'all') === 'all' ? 'bg-amber-400 text-slate-950 font-black' : 'text-slate-400 hover:text-white hover:bg-slate-900' }}">
|
||||
<span>Alle Producten</span>
|
||||
<span class="px-1.5 py-0.5 rounded text-[10px] {{ ($tab ?? 'all') === 'all' ? 'bg-slate-950 text-amber-400 font-black' : 'bg-slate-800 text-slate-400' }}">
|
||||
{{ $totalAll ?? $products->total() }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.products.index', array_merge(request()->except('tab', 'page'), ['tab' => 'published'])) }}" class="px-4 py-2 rounded-xl transition flex items-center space-x-1.5 {{ ($tab ?? 'all') === 'published' ? 'bg-emerald-500 text-white font-black' : 'text-slate-400 hover:text-white hover:bg-slate-900' }}">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
|
||||
<span>Live in Webshop</span>
|
||||
<span class="px-1.5 py-0.5 rounded text-[10px] {{ ($tab ?? 'all') === 'published' ? 'bg-slate-950 text-emerald-400 font-black' : 'bg-slate-800 text-slate-400' }}">
|
||||
{{ $totalPublished ?? 0 }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.products.index', array_merge(request()->except('tab', 'page'), ['tab' => 'draft'])) }}" class="px-4 py-2 rounded-xl transition flex items-center space-x-1.5 {{ ($tab ?? 'all') === 'draft' ? 'bg-amber-400 text-slate-950 font-black' : 'text-slate-400 hover:text-white hover:bg-slate-900' }}">
|
||||
<i data-lucide="layers" class="w-3.5 h-3.5"></i>
|
||||
<span>Concepten / MakerWorld Staging</span>
|
||||
@if(($totalDraft ?? 0) > 0)
|
||||
<span class="px-1.5 py-0.5 rounded text-[10px] bg-amber-400 text-slate-950 font-black">
|
||||
{{ $totalDraft }}
|
||||
</span>
|
||||
@endif
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.products.index', array_merge(request()->except('tab', 'page'), ['tab' => 'makerworld'])) }}" class="px-4 py-2 rounded-xl transition flex items-center space-x-1.5 {{ ($tab ?? 'all') === 'makerworld' ? 'bg-indigo-600 text-white font-black' : 'text-slate-400 hover:text-white hover:bg-slate-900' }}">
|
||||
<i data-lucide="download-cloud" class="w-3.5 h-3.5"></i>
|
||||
<span>MakerWorld Modellen</span>
|
||||
<span class="px-1.5 py-0.5 rounded text-[10px] {{ ($tab ?? 'all') === 'makerworld' ? 'bg-slate-950 text-indigo-300 font-black' : 'bg-slate-800 text-slate-400' }}">
|
||||
{{ $totalMakerWorld ?? 0 }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
@if(($totalDraft ?? 0) > 0)
|
||||
<div class="ml-auto">
|
||||
<a href="{{ route('admin.makerworld.staging') }}" class="px-3 py-1.5 rounded-xl bg-amber-400 hover:bg-amber-300 text-slate-950 font-black text-xs transition flex items-center space-x-1.5 shadow-sm">
|
||||
<i data-lucide="layers" class="w-3.5 h-3.5"></i>
|
||||
<span>Naar Artikel Beheer ({{ $totalDraft }} nieuw) →</span>
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Search & Filters -->
|
||||
<div class="p-4 rounded-2xl bg-slate-900 border border-slate-800 flex flex-col sm:flex-row items-center justify-between gap-3">
|
||||
<form action="{{ route('admin.products.index') }}" method="GET" class="w-full sm:w-auto flex-1 flex items-center space-x-2">
|
||||
@@ -109,11 +154,19 @@
|
||||
</td>
|
||||
|
||||
<td class="py-3.5 px-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
@if($product->is_active)
|
||||
<div class="flex flex-col gap-1 items-start">
|
||||
@if($product->status === 'draft')
|
||||
<span class="inline-flex items-center space-x-1 px-2 py-0.5 rounded-md bg-amber-400/20 text-amber-300 border border-amber-400/40 text-[10px] font-black uppercase tracking-wider">
|
||||
<i data-lucide="layers" class="w-3 h-3"></i>
|
||||
<span>Concept Staging</span>
|
||||
</span>
|
||||
<a href="{{ route('admin.makerworld.staging') }}" class="text-[10px] text-amber-400 hover:underline font-bold">
|
||||
Beoordeel in Artikel Beheer →
|
||||
</a>
|
||||
@elseif($product->is_active)
|
||||
<span class="inline-flex items-center space-x-1 text-emerald-400 text-[11px] font-bold">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400"></span>
|
||||
<span>Actief</span>
|
||||
<span>Live in Webshop</span>
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center space-x-1 text-slate-500 text-[11px] font-bold">
|
||||
@@ -122,6 +175,12 @@
|
||||
</span>
|
||||
@endif
|
||||
|
||||
@if($product->makerworld_id)
|
||||
<span class="text-[10px] text-indigo-400 font-mono">
|
||||
MakerWorld #{{ $product->makerworld_id }}
|
||||
</span>
|
||||
@endif
|
||||
|
||||
@if($product->is_featured)
|
||||
<span class="inline-flex items-center text-amber-400 text-[10px] font-bold">
|
||||
★ Uitgelicht
|
||||
|
||||
Reference in New Issue
Block a user