Compare commits
1 Commits
fix/respon
...
feature/se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fb5747aa2 |
@@ -82,6 +82,18 @@ class OrderController extends Controller
|
||||
|
||||
$orders = $query->paginate(20)->withQueryString();
|
||||
|
||||
// Return JSON for AJAX/API requests (live search)
|
||||
if ($request->wantsJson()) {
|
||||
return response()->json([
|
||||
'data' => $orders->map(fn ($o) => [
|
||||
'order_number' => $o->order_number,
|
||||
'name' => $o->order_number.' - '.$o->business->name,
|
||||
'customer' => $o->business->name,
|
||||
'status' => $o->status,
|
||||
])->values()->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
return view('seller.orders.index', compact('orders', 'business'));
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,18 @@ class AccountController extends Controller
|
||||
|
||||
$accounts = $query->orderBy('name')->paginate(25);
|
||||
|
||||
// Return JSON for AJAX/API requests (live search)
|
||||
if ($request->wantsJson()) {
|
||||
return response()->json([
|
||||
'data' => $accounts->map(fn ($a) => [
|
||||
'slug' => $a->slug,
|
||||
'name' => $a->name,
|
||||
'email' => $a->business_email,
|
||||
'status' => $a->status,
|
||||
])->values()->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
return view('seller.crm.accounts.index', compact('business', 'accounts'));
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,18 @@ class ContactController extends Controller
|
||||
->paginate(25)
|
||||
->withQueryString();
|
||||
|
||||
// Return JSON for AJAX/API requests (live search)
|
||||
if ($request->wantsJson()) {
|
||||
return response()->json([
|
||||
'data' => $contacts->map(fn ($c) => [
|
||||
'hashid' => $c->hashid,
|
||||
'name' => $c->getFullName(),
|
||||
'email' => $c->email,
|
||||
'account' => $c->business?->name,
|
||||
])->values()->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Get accounts for filter dropdown
|
||||
$accounts = Business::where('type', 'buyer')
|
||||
->where('status', 'approved')
|
||||
|
||||
@@ -35,6 +35,19 @@ class LeadController extends Controller
|
||||
|
||||
$leads = $query->latest()->paginate(25);
|
||||
|
||||
// Return JSON for AJAX/API requests (live search)
|
||||
if ($request->wantsJson()) {
|
||||
return response()->json([
|
||||
'data' => $leads->map(fn ($l) => [
|
||||
'hashid' => $l->hashid,
|
||||
'name' => $l->company_name,
|
||||
'contact' => $l->contact_name,
|
||||
'email' => $l->contact_email,
|
||||
'status' => $l->status,
|
||||
])->values()->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
return view('seller.crm.leads.index', compact('business', 'leads'));
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,19 @@ class QuoteController extends Controller
|
||||
|
||||
$quotes = $query->orderByDesc('created_at')->paginate(25);
|
||||
|
||||
// Return JSON for AJAX/API requests (live search)
|
||||
if ($request->wantsJson()) {
|
||||
return response()->json([
|
||||
'data' => $quotes->map(fn ($q) => [
|
||||
'id' => $q->id,
|
||||
'name' => $q->quote_number.' - '.($q->title ?? 'Untitled'),
|
||||
'contact' => $q->contact?->name ?? '-',
|
||||
'status' => $q->status,
|
||||
'total' => '$'.number_format($q->total, 2),
|
||||
])->values()->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
return view('seller.crm.quotes.index', compact('quotes', 'business'));
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,30 @@ class TaskController extends Controller
|
||||
$tasksQuery->where('type', $request->type);
|
||||
}
|
||||
|
||||
// Search filter
|
||||
if ($request->filled('q')) {
|
||||
$search = $request->q;
|
||||
$tasksQuery->where(function ($q) use ($search) {
|
||||
$q->where('title', 'ILIKE', "%{$search}%")
|
||||
->orWhere('details', 'ILIKE', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
$tasks = $tasksQuery->paginate(25);
|
||||
|
||||
// Return JSON for AJAX/API requests (live search)
|
||||
if ($request->wantsJson()) {
|
||||
return response()->json([
|
||||
'data' => $tasks->map(fn ($t) => [
|
||||
'id' => $t->id,
|
||||
'name' => $t->title,
|
||||
'type' => $t->type,
|
||||
'assignee' => $t->assignee?->name ?? 'Unassigned',
|
||||
'due_at' => $t->due_at?->format('M j, Y'),
|
||||
])->values()->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Get stats with single efficient query
|
||||
$statsQuery = CrmTask::where('seller_business_id', $business->id)
|
||||
->selectRaw('
|
||||
|
||||
@@ -167,6 +167,19 @@ class InvoiceController extends Controller
|
||||
->paginate(25)
|
||||
->withQueryString();
|
||||
|
||||
// Return JSON for AJAX/API requests (live search)
|
||||
if ($request->wantsJson()) {
|
||||
return response()->json([
|
||||
'data' => $invoices->map(fn ($i) => [
|
||||
'hashid' => $i->hashid,
|
||||
'name' => $i->invoice_number.' - '.$i->business->name,
|
||||
'invoice_number' => $i->invoice_number,
|
||||
'customer' => $i->business->name,
|
||||
'status' => $i->payment_status,
|
||||
])->values()->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
return view('seller.invoices.index', compact('business', 'invoices', 'stats'));
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,11 @@ class ProductController extends Controller
|
||||
// Get brand IDs to filter by (respects brand context switcher)
|
||||
$brandIds = BrandSwitcherController::getFilteredBrandIds();
|
||||
|
||||
// Get all brands for the business for the filter dropdown
|
||||
$brands = \App\Models\Brand::where('business_id', $business->id)
|
||||
->orderBy('name')
|
||||
->get(['id', 'name']);
|
||||
|
||||
// Calculate missing BOM count for health alert
|
||||
$missingBomCount = Product::whereIn('brand_id', $brandIds)
|
||||
->where('is_assembly', true)
|
||||
@@ -150,7 +155,20 @@ class ProductController extends Controller
|
||||
'to' => $paginator->lastItem(),
|
||||
];
|
||||
|
||||
return view('seller.products.index', compact('business', 'products', 'missingBomCount', 'paginator', 'pagination'));
|
||||
// Return JSON for AJAX/API requests (live search)
|
||||
if ($request->wantsJson()) {
|
||||
return response()->json([
|
||||
'data' => $products->map(fn ($p) => [
|
||||
'hashid' => $p['hashid'],
|
||||
'name' => $p['product'],
|
||||
'sku' => $p['sku'],
|
||||
'brand' => $p['brand'],
|
||||
])->values()->toArray(),
|
||||
'pagination' => $pagination,
|
||||
]);
|
||||
}
|
||||
|
||||
return view('seller.products.index', compact('business', 'brands', 'products', 'missingBomCount', 'paginator', 'pagination'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,22 @@
|
||||
alpine
|
||||
clear-action="clearFilters"
|
||||
>
|
||||
|
||||
For Live Search with dropdown (recommended):
|
||||
<x-ui.filter-bar
|
||||
search-placeholder="Search products..."
|
||||
:search-value="request('search')"
|
||||
live-search-url="{{ route('seller.business.products.index', $business->slug) }}"
|
||||
live-search-label="name"
|
||||
live-search-sublabel="sku"
|
||||
live-search-route-prefix="{{ route('seller.business.products.show', [$business->slug, '__ID__']) }}"
|
||||
clear-url="{{ route('seller.business.products.index', $business->slug) }}"
|
||||
>
|
||||
Props:
|
||||
- live-search-url: API endpoint that returns JSON with 'data' array
|
||||
- live-search-label: Primary field to display (default: 'name')
|
||||
- live-search-sublabel: Secondary field to display (optional, e.g., 'sku')
|
||||
- live-search-route-prefix: URL template with __ID__ replaced by item hashid/id
|
||||
--}}
|
||||
@props([
|
||||
'searchPlaceholder' => 'Search...',
|
||||
@@ -45,9 +61,144 @@
|
||||
'alpine' => false,
|
||||
'formAction' => null,
|
||||
'formMethod' => 'GET',
|
||||
'liveSearchUrl' => null,
|
||||
'liveSearchLabel' => 'name',
|
||||
'liveSearchSublabel' => null,
|
||||
'liveSearchRoutePrefix' => null,
|
||||
])
|
||||
|
||||
@if($alpine)
|
||||
@if($liveSearchUrl)
|
||||
{{-- Live Search Mode with Dropdown --}}
|
||||
<div x-data="{
|
||||
liveSearchQuery: '{{ $searchValue ?? '' }}',
|
||||
liveSearchResults: [],
|
||||
liveSearchOpen: false,
|
||||
liveSearchLoading: false,
|
||||
liveSearchIndex: -1,
|
||||
liveSearchUrl: '{{ $liveSearchUrl }}',
|
||||
liveSearchRoutePrefix: '{{ $liveSearchRoutePrefix }}',
|
||||
liveSearchLabelField: '{{ $liveSearchLabel }}',
|
||||
liveSearchSublabelField: '{{ $liveSearchSublabel }}',
|
||||
|
||||
|
||||
async doLiveSearch() {
|
||||
if (this.liveSearchQuery.length < 2) {
|
||||
this.liveSearchResults = [];
|
||||
this.liveSearchOpen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.liveSearchLoading = true;
|
||||
try {
|
||||
let url = this.liveSearchUrl;
|
||||
if (url.includes('?')) {
|
||||
url = url + encodeURIComponent(this.liveSearchQuery);
|
||||
} else {
|
||||
url = url + '?search=' + encodeURIComponent(this.liveSearchQuery);
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
const data = await response.json();
|
||||
this.liveSearchResults = data.data || data || [];
|
||||
this.liveSearchOpen = this.liveSearchResults.length > 0;
|
||||
this.liveSearchIndex = -1;
|
||||
} catch (error) {
|
||||
console.error('Live search error:', error);
|
||||
this.liveSearchResults = [];
|
||||
}
|
||||
this.liveSearchLoading = false;
|
||||
},
|
||||
|
||||
navigateLiveSearch(item) {
|
||||
const id = item.hashid || item.slug || item.id || item.order_number;
|
||||
if (this.liveSearchRoutePrefix && id) {
|
||||
window.location.href = this.liveSearchRoutePrefix.replace('__ID__', id);
|
||||
}
|
||||
},
|
||||
|
||||
handleLiveSearchKeydown(event) {
|
||||
if (!this.liveSearchOpen) return;
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
this.liveSearchIndex = Math.min(this.liveSearchIndex + 1, this.liveSearchResults.length - 1);
|
||||
} else if (event.key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
this.liveSearchIndex = Math.max(this.liveSearchIndex - 1, -1);
|
||||
} else if (event.key === 'Enter' && this.liveSearchIndex >= 0) {
|
||||
event.preventDefault();
|
||||
this.navigateLiveSearch(this.liveSearchResults[this.liveSearchIndex]);
|
||||
} else if (event.key === 'Escape') {
|
||||
this.liveSearchOpen = false;
|
||||
}
|
||||
},
|
||||
|
||||
getLiveSearchLabel(item) {
|
||||
return item[this.liveSearchLabelField] || item.name || '';
|
||||
},
|
||||
|
||||
getLiveSearchSublabel(item) {
|
||||
if (!this.liveSearchSublabelField) return null;
|
||||
return item[this.liveSearchSublabelField] || null;
|
||||
}
|
||||
}" x-on:keydown="handleLiveSearchKeydown" x-on:click.outside="liveSearchOpen = false"
|
||||
class="{{ $attributes->get('class', 'rounded-lg border border-base-300 bg-base-100 px-4 py-3 flex flex-col gap-3 md:flex-row md:items-center md:justify-between') }}">
|
||||
{{-- Left: Search + Filters --}}
|
||||
<div class="flex flex-1 gap-2 items-center">
|
||||
<div class="relative flex-1 max-w-sm">
|
||||
<input type="text"
|
||||
x-model="liveSearchQuery"
|
||||
x-on:input.debounce.300ms="doLiveSearch()"
|
||||
placeholder="{{ $searchPlaceholder }}"
|
||||
x-on:focus="if (liveSearchResults.length > 0) liveSearchOpen = true"
|
||||
class="input input-sm input-bordered w-full bg-base-100">
|
||||
<template x-if="liveSearchLoading">
|
||||
<span class="absolute right-2.5 top-1/2 -translate-y-1/2">
|
||||
<span class="loading loading-spinner loading-xs text-base-content/30"></span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
{{-- Live Search Dropdown --}}
|
||||
<div class="absolute z-[9999] left-0 right-0 top-full mt-1 bg-base-100 border border-base-300 rounded-lg shadow-lg max-h-72 overflow-hidden"
|
||||
x-bind:style="liveSearchOpen ? '' : 'display: none'">
|
||||
<div class="px-3 py-1.5 border-b border-base-200 bg-base-200/30">
|
||||
<span class="text-xs font-medium text-base-content/50">Search Results</span>
|
||||
</div>
|
||||
<ul class="menu menu-sm p-1 max-h-56 overflow-y-auto">
|
||||
<template x-for="(item, index) in liveSearchResults" :key="item.hashid || item.slug || item.id || index">
|
||||
<li>
|
||||
<a @click.prevent="navigateLiveSearch(item)"
|
||||
:class="{ 'active': liveSearchIndex === index }"
|
||||
class="flex flex-col items-start py-2 cursor-pointer">
|
||||
<span class="font-medium text-sm" x-text="getLiveSearchLabel(item)"></span>
|
||||
<template x-if="getLiveSearchSublabel(item)">
|
||||
<span class="text-xs text-base-content/60" x-text="getLiveSearchSublabel(item)"></span>
|
||||
</template>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ $filters ?? '' }}
|
||||
</div>
|
||||
|
||||
{{-- Right: Actions + Clear --}}
|
||||
<div class="flex items-center gap-2">
|
||||
{{ $actions ?? '' }}
|
||||
|
||||
@if($clearUrl)
|
||||
<a href="{{ $clearUrl }}" class="btn btn-ghost btn-xs text-xs text-base-content/50 hover:text-base-content">Clear</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@elseif($alpine)
|
||||
<div {{ $attributes->merge(['class' => 'rounded-lg border border-base-300 bg-base-100 px-4 py-3 flex flex-col gap-3 md:flex-row md:items-center md:justify-between']) }}>
|
||||
{{-- Left: Search + Filters --}}
|
||||
<div class="flex flex-1 gap-2 items-center">
|
||||
@@ -55,10 +206,22 @@
|
||||
<input type="text"
|
||||
x-model.debounce.200ms="{{ $searchModel }}"
|
||||
placeholder="{{ $searchPlaceholder }}"
|
||||
@if($formAction)
|
||||
x-on:keydown.enter="window.location.href='{{ $formAction }}' + '?search=' + encodeURIComponent({{ $searchModel }})"
|
||||
@endif
|
||||
class="input input-sm input-bordered w-full pr-8 bg-base-100">
|
||||
@if($formAction)
|
||||
<button type="button"
|
||||
x-on:click="window.location.href='{{ $formAction }}' + '?search=' + encodeURIComponent({{ $searchModel }})"
|
||||
class="absolute right-2.5 top-1/2 -translate-y-1/2"
|
||||
title="Search all products (Enter)">
|
||||
<span class="icon-[heroicons--magnifying-glass] size-4 text-base-content/30 hover:text-base-content/60"></span>
|
||||
</button>
|
||||
@else
|
||||
<span class="absolute right-2.5 top-1/2 -translate-y-1/2">
|
||||
<span class="icon-[heroicons--magnifying-glass] size-4 text-base-content/30"></span>
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{ $filters ?? '' }}
|
||||
|
||||
@@ -48,18 +48,20 @@
|
||||
|
||||
{{-- Filter Toolbar --}}
|
||||
<x-ui.filter-bar
|
||||
form-action="{{ route('seller.business.crm.accounts.index', $business->slug) }}"
|
||||
search-placeholder="Search accounts..."
|
||||
search-name="q"
|
||||
:search-value="request('q')"
|
||||
search-name="q"
|
||||
form-action="{{ route('seller.business.crm.accounts.index', $business->slug) }}"
|
||||
clear-url="{{ route('seller.business.crm.accounts.index', $business->slug) }}"
|
||||
>
|
||||
<x-slot:filters>
|
||||
<select name="status" class="select select-sm select-bordered w-36" onchange="this.form.submit()">
|
||||
<option value="">All Status</option>
|
||||
<option value="active" {{ request('status') === 'active' ? 'selected' : '' }}>Active</option>
|
||||
<option value="inactive" {{ request('status') === 'inactive' ? 'selected' : '' }}>Inactive</option>
|
||||
</select>
|
||||
<form method="GET" action="{{ route('seller.business.crm.accounts.index', $business->slug) }}" class="contents">
|
||||
<select name="status" class="select select-sm select-bordered w-36" onchange="this.form.submit()">
|
||||
<option value="">All Status</option>
|
||||
<option value="active" {{ request('status') === 'active' ? 'selected' : '' }}>Active</option>
|
||||
<option value="inactive" {{ request('status') === 'inactive' ? 'selected' : '' }}>Inactive</option>
|
||||
</select>
|
||||
</form>
|
||||
</x-slot:filters>
|
||||
</x-ui.filter-bar>
|
||||
|
||||
|
||||
@@ -22,32 +22,34 @@
|
||||
|
||||
{{-- Filter Toolbar --}}
|
||||
<x-ui.filter-bar
|
||||
form-action="{{ route('seller.business.crm.contacts.index', $business->slug) }}"
|
||||
search-placeholder="Search contacts..."
|
||||
search-name="q"
|
||||
:search-value="request('q')"
|
||||
search-name="q"
|
||||
form-action="{{ route('seller.business.crm.contacts.index', $business->slug) }}"
|
||||
clear-url="{{ route('seller.business.crm.contacts.index', $business->slug) }}"
|
||||
>
|
||||
<x-slot:filters>
|
||||
<select name="account" class="select select-sm select-bordered w-48" onchange="this.form.submit()">
|
||||
<option value="">All Accounts</option>
|
||||
@foreach($accounts as $account)
|
||||
<option value="{{ $account->id }}" {{ request('account') == $account->id ? 'selected' : '' }}>
|
||||
{{ $account->dba_name ?: $account->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="type" class="select select-sm select-bordered w-36" onchange="this.form.submit()">
|
||||
<option value="">All Types</option>
|
||||
@foreach(\App\Models\Contact::CONTACT_TYPES as $key => $label)
|
||||
<option value="{{ $key }}" {{ request('type') === $key ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="status" class="select select-sm select-bordered w-32" onchange="this.form.submit()">
|
||||
<option value="">Active</option>
|
||||
<option value="inactive" {{ request('status') === 'inactive' ? 'selected' : '' }}>Inactive</option>
|
||||
<option value="all" {{ request('status') === 'all' ? 'selected' : '' }}>All</option>
|
||||
</select>
|
||||
<form method="GET" action="{{ route('seller.business.crm.contacts.index', $business->slug) }}" class="contents">
|
||||
<select name="account" class="select select-sm select-bordered w-48" onchange="this.form.submit()">
|
||||
<option value="">All Accounts</option>
|
||||
@foreach($accounts as $account)
|
||||
<option value="{{ $account->id }}" {{ request('account') == $account->id ? 'selected' : '' }}>
|
||||
{{ $account->dba_name ?: $account->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="type" class="select select-sm select-bordered w-36" onchange="this.form.submit()">
|
||||
<option value="">All Types</option>
|
||||
@foreach(\App\Models\Contact::CONTACT_TYPES as $key => $label)
|
||||
<option value="{{ $key }}" {{ request('type') === $key ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="status" class="select select-sm select-bordered w-32" onchange="this.form.submit()">
|
||||
<option value="">Active</option>
|
||||
<option value="inactive" {{ request('status') === 'inactive' ? 'selected' : '' }}>Inactive</option>
|
||||
<option value="all" {{ request('status') === 'all' ? 'selected' : '' }}>All</option>
|
||||
</select>
|
||||
</form>
|
||||
</x-slot:filters>
|
||||
</x-ui.filter-bar>
|
||||
|
||||
|
||||
@@ -29,31 +29,24 @@
|
||||
</div>
|
||||
|
||||
{{-- Search & Filters --}}
|
||||
<div class="card bg-base-100 shadow-sm mb-6">
|
||||
<div class="card-body p-4">
|
||||
<form method="GET" class="flex flex-wrap items-center gap-4">
|
||||
<div class="form-control flex-1 min-w-64">
|
||||
<input type="text"
|
||||
name="q"
|
||||
value="{{ request('q') }}"
|
||||
placeholder="Search leads..."
|
||||
class="input input-sm input-bordered w-full">
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<select name="status" class="select select-sm select-bordered">
|
||||
<option value="">All Status</option>
|
||||
@foreach(\App\Models\Crm\CrmLead::STATUSES as $value => $label)
|
||||
<option value="{{ $value }}" {{ request('status') === $value ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-sm btn-primary">
|
||||
<span class="icon-[heroicons--magnifying-glass] size-4"></span>
|
||||
Search
|
||||
</button>
|
||||
<x-ui.filter-bar
|
||||
search-placeholder="Search leads..."
|
||||
:search-value="request('q')"
|
||||
search-name="q"
|
||||
form-action="{{ route('seller.business.crm.leads.index', $business->slug) }}"
|
||||
clear-url="{{ route('seller.business.crm.leads.index', $business->slug) }}"
|
||||
>
|
||||
<x-slot:filters>
|
||||
<form method="GET" action="{{ route('seller.business.crm.leads.index', $business->slug) }}" class="contents">
|
||||
<select name="status" class="select select-sm select-bordered w-36" onchange="this.form.submit()">
|
||||
<option value="">All Status</option>
|
||||
@foreach(\App\Models\Crm\CrmLead::STATUSES as $value => $label)
|
||||
<option value="{{ $value }}" {{ request('status') === $value ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</x-slot:filters>
|
||||
</x-ui.filter-bar>
|
||||
|
||||
{{-- Leads List --}}
|
||||
@if($leads->isEmpty())
|
||||
|
||||
@@ -18,20 +18,23 @@
|
||||
|
||||
{{-- Search & Filter Toolbar --}}
|
||||
<x-ui.filter-bar
|
||||
form-action="{{ route('seller.business.crm.quotes.index', $business) }}"
|
||||
search-placeholder="Search quotes..."
|
||||
:search-value="request('search')"
|
||||
search-name="search"
|
||||
form-action="{{ route('seller.business.crm.quotes.index', $business) }}"
|
||||
clear-url="{{ route('seller.business.crm.quotes.index', $business) }}"
|
||||
>
|
||||
<x-slot:filters>
|
||||
<select name="status" class="select select-sm select-bordered w-36" onchange="this.form.submit()">
|
||||
<option value="">All Status</option>
|
||||
<option value="draft" {{ request('status') === 'draft' ? 'selected' : '' }}>Draft</option>
|
||||
<option value="sent" {{ request('status') === 'sent' ? 'selected' : '' }}>Sent</option>
|
||||
<option value="accepted" {{ request('status') === 'accepted' ? 'selected' : '' }}>Accepted</option>
|
||||
<option value="declined" {{ request('status') === 'declined' ? 'selected' : '' }}>Declined</option>
|
||||
<option value="expired" {{ request('status') === 'expired' ? 'selected' : '' }}>Expired</option>
|
||||
</select>
|
||||
<form method="GET" action="{{ route('seller.business.crm.quotes.index', $business) }}" class="contents">
|
||||
<select name="status" class="select select-sm select-bordered w-36" onchange="this.form.submit()">
|
||||
<option value="">All Status</option>
|
||||
<option value="draft" {{ request('status') === 'draft' ? 'selected' : '' }}>Draft</option>
|
||||
<option value="sent" {{ request('status') === 'sent' ? 'selected' : '' }}>Sent</option>
|
||||
<option value="accepted" {{ request('status') === 'accepted' ? 'selected' : '' }}>Accepted</option>
|
||||
<option value="declined" {{ request('status') === 'declined' ? 'selected' : '' }}>Declined</option>
|
||||
<option value="expired" {{ request('status') === 'expired' ? 'selected' : '' }}>Expired</option>
|
||||
</select>
|
||||
</form>
|
||||
</x-slot:filters>
|
||||
</x-ui.filter-bar>
|
||||
|
||||
|
||||
@@ -44,36 +44,40 @@
|
||||
|
||||
{{-- Filters --}}
|
||||
<x-ui.filter-bar
|
||||
form-action="{{ route('seller.business.crm.tasks.index', $business->slug) }}"
|
||||
search-placeholder="Search tasks..."
|
||||
:search-value="request('q')"
|
||||
search-name="q"
|
||||
form-action="{{ route('seller.business.crm.tasks.index', $business->slug) }}"
|
||||
clear-url="{{ route('seller.business.crm.tasks.index', $business->slug) }}"
|
||||
>
|
||||
<x-slot:filters>
|
||||
<select name="assignee" class="select select-sm select-bordered w-40" onchange="this.form.submit()">
|
||||
<option value="">All Team</option>
|
||||
<option value="me" {{ request('assignee') === 'me' ? 'selected' : '' }}>My Tasks</option>
|
||||
@foreach($teamMembers as $member)
|
||||
<option value="{{ $member->id }}" {{ request('assignee') == $member->id ? 'selected' : '' }}>
|
||||
{{ $member->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="business_id" class="select select-sm select-bordered w-40" onchange="this.form.submit()">
|
||||
<option value="">All Accounts</option>
|
||||
@foreach($buyerBusinesses as $buyer)
|
||||
<option value="{{ $buyer->id }}" {{ request('business_id') == $buyer->id ? 'selected' : '' }}>
|
||||
{{ $buyer->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="priority" class="select select-sm select-bordered w-32" onchange="this.form.submit()">
|
||||
<option value="">All Priority</option>
|
||||
@foreach(\App\Models\Crm\CrmTask::PRIORITIES as $key => $label)
|
||||
<option value="{{ $key }}" {{ request('priority') === $key ? 'selected' : '' }}>
|
||||
{{ $label }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<form method="GET" action="{{ route('seller.business.crm.tasks.index', $business->slug) }}" class="contents">
|
||||
<select name="assignee" class="select select-sm select-bordered w-40" onchange="this.form.submit()">
|
||||
<option value="">All Team</option>
|
||||
<option value="me" {{ request('assignee') === 'me' ? 'selected' : '' }}>My Tasks</option>
|
||||
@foreach($teamMembers as $member)
|
||||
<option value="{{ $member->id }}" {{ request('assignee') == $member->id ? 'selected' : '' }}>
|
||||
{{ $member->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="business_id" class="select select-sm select-bordered w-40" onchange="this.form.submit()">
|
||||
<option value="">All Accounts</option>
|
||||
@foreach($buyerBusinesses as $buyer)
|
||||
<option value="{{ $buyer->id }}" {{ request('business_id') == $buyer->id ? 'selected' : '' }}>
|
||||
{{ $buyer->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="priority" class="select select-sm select-bordered w-32" onchange="this.form.submit()">
|
||||
<option value="">All Priority</option>
|
||||
@foreach(\App\Models\Crm\CrmTask::PRIORITIES as $key => $label)
|
||||
<option value="{{ $key }}" {{ request('priority') === $key ? 'selected' : '' }}>
|
||||
{{ $label }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</form>
|
||||
</x-slot:filters>
|
||||
</x-ui.filter-bar>
|
||||
|
||||
|
||||
@@ -16,19 +16,21 @@
|
||||
|
||||
{{-- Filter Bar --}}
|
||||
<x-ui.filter-bar
|
||||
form-action="{{ route('seller.business.invoices.index', $business->slug) }}"
|
||||
search-placeholder="Search invoices..."
|
||||
search-name="search"
|
||||
:search-value="request('search')"
|
||||
search-name="search"
|
||||
form-action="{{ route('seller.business.invoices.index', $business->slug) }}"
|
||||
clear-url="{{ route('seller.business.invoices.index', $business->slug) }}"
|
||||
>
|
||||
<x-slot:filters>
|
||||
<select name="status" class="select select-sm select-bordered w-36" onchange="this.form.submit()">
|
||||
<option value="">All Status</option>
|
||||
<option value="unpaid" {{ request('status') === 'unpaid' ? 'selected' : '' }}>Unpaid</option>
|
||||
<option value="paid" {{ request('status') === 'paid' ? 'selected' : '' }}>Paid</option>
|
||||
<option value="overdue" {{ request('status') === 'overdue' ? 'selected' : '' }}>Overdue</option>
|
||||
</select>
|
||||
<form method="GET" action="{{ route('seller.business.invoices.index', $business->slug) }}" class="contents">
|
||||
<select name="status" class="select select-sm select-bordered w-36" onchange="this.form.submit()">
|
||||
<option value="">All Status</option>
|
||||
<option value="unpaid" {{ request('status') === 'unpaid' ? 'selected' : '' }}>Unpaid</option>
|
||||
<option value="paid" {{ request('status') === 'paid' ? 'selected' : '' }}>Paid</option>
|
||||
<option value="overdue" {{ request('status') === 'overdue' ? 'selected' : '' }}>Overdue</option>
|
||||
</select>
|
||||
</form>
|
||||
</x-slot:filters>
|
||||
</x-ui.filter-bar>
|
||||
|
||||
|
||||
@@ -12,33 +12,36 @@
|
||||
|
||||
{{-- Search + Filter Bar --}}
|
||||
<x-ui.filter-bar
|
||||
form-action="{{ route('seller.business.orders.index', $business->slug) }}"
|
||||
search-placeholder="Search order # or customer..."
|
||||
:search-value="request('search')"
|
||||
search-name="search"
|
||||
form-action="{{ route('seller.business.orders.index', $business->slug) }}"
|
||||
clear-url="{{ route('seller.business.orders.index', $business->slug) }}"
|
||||
>
|
||||
<x-slot:filters>
|
||||
<select name="status" class="select select-sm select-bordered w-36" onchange="this.form.submit()">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="new" {{ request('status') === 'new' ? 'selected' : '' }}>New</option>
|
||||
<option value="accepted" {{ request('status') === 'accepted' ? 'selected' : '' }}>Accepted</option>
|
||||
<option value="in_progress" {{ request('status') === 'in_progress' ? 'selected' : '' }}>In Progress</option>
|
||||
<option value="ready_for_manifest" {{ request('status') === 'ready_for_manifest' ? 'selected' : '' }}>Ready for Manifest</option>
|
||||
<option value="ready_for_delivery" {{ request('status') === 'ready_for_delivery' ? 'selected' : '' }}>Buyer Review</option>
|
||||
<option value="approved_for_delivery" {{ request('status') === 'approved_for_delivery' ? 'selected' : '' }}>Order Ready</option>
|
||||
<option value="out_for_delivery" {{ request('status') === 'out_for_delivery' ? 'selected' : '' }}>Out for Delivery</option>
|
||||
<option value="delivered" {{ request('status') === 'delivered' ? 'selected' : '' }}>Delivered</option>
|
||||
<option value="buyer_approved" {{ request('status') === 'buyer_approved' ? 'selected' : '' }}>Completed</option>
|
||||
<option value="rejected" {{ request('status') === 'rejected' ? 'selected' : '' }}>Rejected</option>
|
||||
<option value="cancelled" {{ request('status') === 'cancelled' ? 'selected' : '' }}>Cancelled</option>
|
||||
</select>
|
||||
<form method="GET" action="{{ route('seller.business.orders.index', $business->slug) }}" class="contents">
|
||||
<select name="status" class="select select-sm select-bordered w-36" onchange="this.form.submit()">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="new" {{ request('status') === 'new' ? 'selected' : '' }}>New</option>
|
||||
<option value="accepted" {{ request('status') === 'accepted' ? 'selected' : '' }}>Accepted</option>
|
||||
<option value="in_progress" {{ request('status') === 'in_progress' ? 'selected' : '' }}>In Progress</option>
|
||||
<option value="ready_for_manifest" {{ request('status') === 'ready_for_manifest' ? 'selected' : '' }}>Ready for Manifest</option>
|
||||
<option value="ready_for_delivery" {{ request('status') === 'ready_for_delivery' ? 'selected' : '' }}>Buyer Review</option>
|
||||
<option value="approved_for_delivery" {{ request('status') === 'approved_for_delivery' ? 'selected' : '' }}>Order Ready</option>
|
||||
<option value="out_for_delivery" {{ request('status') === 'out_for_delivery' ? 'selected' : '' }}>Out for Delivery</option>
|
||||
<option value="delivered" {{ request('status') === 'delivered' ? 'selected' : '' }}>Delivered</option>
|
||||
<option value="buyer_approved" {{ request('status') === 'buyer_approved' ? 'selected' : '' }}>Completed</option>
|
||||
<option value="rejected" {{ request('status') === 'rejected' ? 'selected' : '' }}>Rejected</option>
|
||||
<option value="cancelled" {{ request('status') === 'cancelled' ? 'selected' : '' }}>Cancelled</option>
|
||||
</select>
|
||||
|
||||
<select name="workorder_filter" class="select select-sm select-bordered w-32 hidden lg:block" onchange="this.form.submit()">
|
||||
<option value="">Fulfillment</option>
|
||||
<option value="not_started" {{ request('workorder_filter') === 'not_started' ? 'selected' : '' }}>Not Started</option>
|
||||
<option value="in_progress" {{ request('workorder_filter') === 'in_progress' ? 'selected' : '' }}>In Progress</option>
|
||||
<option value="completed" {{ request('workorder_filter') === 'completed' ? 'selected' : '' }}>Completed</option>
|
||||
</select>
|
||||
<select name="workorder_filter" class="select select-sm select-bordered w-32 hidden lg:block" onchange="this.form.submit()">
|
||||
<option value="">Fulfillment</option>
|
||||
<option value="not_started" {{ request('workorder_filter') === 'not_started' ? 'selected' : '' }}>Not Started</option>
|
||||
<option value="in_progress" {{ request('workorder_filter') === 'in_progress' ? 'selected' : '' }}>In Progress</option>
|
||||
<option value="completed" {{ request('workorder_filter') === 'completed' ? 'selected' : '' }}>Completed</option>
|
||||
</select>
|
||||
</form>
|
||||
</x-slot:filters>
|
||||
</x-ui.filter-bar>
|
||||
|
||||
|
||||
@@ -3,11 +3,7 @@
|
||||
@section('content')
|
||||
<div class="max-w-7xl mx-auto px-6 py-4 space-y-4"
|
||||
x-data="{
|
||||
// State - initialize from URL params to sync with server-side filtering
|
||||
search: '{{ request('search', '') }}',
|
||||
brandFilter: '{{ request('brand_id', 'all') ?: 'all' }}',
|
||||
statusFilter: '{{ request('status', 'all') ?: 'all' }}',
|
||||
visibilityFilter: 'all',
|
||||
// State for view controls
|
||||
focusFilter: 'all',
|
||||
viewMode: 'table',
|
||||
selectedProduct: null,
|
||||
@@ -16,7 +12,7 @@
|
||||
// Pagination info from server
|
||||
pagination: @js($pagination),
|
||||
|
||||
// Real product data from database
|
||||
// Product data from database (already filtered by server-side search)
|
||||
listings: @js($products->map(function($product) {
|
||||
$isListed = $product['status'] === 'active' && $product['visibility'] !== 'private';
|
||||
$stock = 0;
|
||||
@@ -33,16 +29,9 @@
|
||||
]);
|
||||
})),
|
||||
|
||||
// Computed properties
|
||||
// Client-side focus filters only (search is server-side)
|
||||
get filteredListings() {
|
||||
return this.listings.filter(listing => {
|
||||
const matchesSearch = !this.search ||
|
||||
listing.product.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||
listing.sku.toLowerCase().includes(this.search.toLowerCase());
|
||||
const matchesBrand = this.brandFilter === 'all' || listing.brand === this.brandFilter;
|
||||
const matchesStatus = this.statusFilter === 'all' || listing.status === this.statusFilter;
|
||||
const matchesVisibility = this.visibilityFilter === 'all' || listing.visibility === this.visibilityFilter;
|
||||
|
||||
let matchesFocus = true;
|
||||
if (this.focusFilter === 'low-stock') {
|
||||
matchesFocus = listing.stock > 0 && listing.stock <= listing.lowStockThreshold;
|
||||
@@ -56,7 +45,7 @@
|
||||
matchesFocus = listing.status === 'draft';
|
||||
}
|
||||
|
||||
return matchesSearch && matchesBrand && matchesStatus && matchesVisibility && matchesFocus;
|
||||
return matchesFocus;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -79,10 +68,6 @@
|
||||
},
|
||||
|
||||
clearFilters() {
|
||||
this.search = '';
|
||||
this.brandFilter = 'all';
|
||||
this.statusFilter = 'all';
|
||||
this.visibilityFilter = 'all';
|
||||
this.focusFilter = 'all';
|
||||
}
|
||||
}">
|
||||
@@ -111,19 +96,19 @@
|
||||
@endcan
|
||||
@endif
|
||||
|
||||
{{-- Search + Filter Bar --}}
|
||||
{{-- Search + Filter Bar - server-side search --}}
|
||||
<x-ui.filter-bar
|
||||
form-action="{{ route('seller.business.products.index', $business->slug) }}"
|
||||
search-placeholder="Search products..."
|
||||
search-name="search"
|
||||
search-placeholder="Search by name or SKU..."
|
||||
:search-value="request('search')"
|
||||
search-name="search"
|
||||
form-action="{{ route('seller.business.products.index', $business->slug) }}"
|
||||
clear-url="{{ route('seller.business.products.index', $business->slug) }}"
|
||||
>
|
||||
<x-slot:filters>
|
||||
<select name="brand_id" class="select select-sm select-bordered w-32" onchange="this.form.submit()">
|
||||
<option value="">All Brands</option>
|
||||
@foreach($products->pluck('brand')->unique()->sort() as $brand)
|
||||
<option value="{{ $products->where('brand', $brand)->first()['id'] ?? '' }}" {{ request('brand_id') == ($products->where('brand', $brand)->first()['id'] ?? '') ? 'selected' : '' }}>{{ $brand }}</option>
|
||||
@foreach($brands as $brand)
|
||||
<option value="{{ $brand->id }}" {{ request('brand_id') == $brand->id ? 'selected' : '' }}>{{ $brand->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="status" class="select select-sm select-bordered w-28 hidden lg:block" onchange="this.form.submit()">
|
||||
@@ -461,6 +446,4 @@
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
@endpush
|
||||
{{-- Alpine.js is already included in the main app bundle - no need for CDN --}}
|
||||
|
||||
Reference in New Issue
Block a user