[FEAT] 於機台管理與機台設定頁面新增公司篩選功能

1. 於機台管理 (MachineController 與 index.blade.php) 為系統管理員新增公司篩選下拉選單,並支援分頁參數與 AJAX 查詢。
2. 於機台設定頁面中的機台列表 (tab-machines.blade.php) 與系統設定列表 (tab-system-settings.blade.php) 新增公司篩選器。
3. 在 MachineSettingController 傳遞公司列表資料至視圖,以便支援公司篩選。
4. 修正重置按鈕,使其能正確清除 Preline HSSelect 的選取狀態並重新載入資料。
This commit is contained in:
sky121113 2026-06-05 13:01:52 +08:00
parent 9a59186cd4
commit e414396b0c
6 changed files with 151 additions and 36 deletions

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin\BasicSettings;
use App\Http\Controllers\Admin\AdminController;
use App\Models\Machine\Machine;
use App\Models\Machine\MachineModel;
use App\Models\System\Company;
use App\Models\System\PaymentConfig;
use App\Traits\ImageHandler;
use Illuminate\Http\Request;
@ -28,6 +29,23 @@ class MachineSettingController extends AdminController
$per_page = $request->input('per_page', 10);
$search = $request->input('search');
$isAjax = $request->boolean('_ajax');
$currentUser = auth()->user();
$companyId = trim((string) $request->input('company_id', ''));
$applyMachineFilters = function ($query) use ($search, $currentUser, $companyId) {
if ($search) {
$query->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('serial_no', 'like', "%{$search}%");
});
}
if ($currentUser->isSystemAdmin() && $companyId !== '') {
$query->where('company_id', $companyId);
}
return $query;
};
// AJAX 模式:只查當前 Tab 的搜尋/分頁結果
if ($isAjax) {
@ -38,23 +56,13 @@ class MachineSettingController extends AdminController
switch ($tab) {
case 'machines':
$machineQuery = Machine::query()->with(['machineModel', 'paymentConfig', 'company']);
if ($search) {
$machineQuery->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('serial_no', 'like', "%{$search}%");
});
}
$applyMachineFilters($machineQuery);
$machines = $machineQuery->latest()->paginate($per_page)->withQueryString();
break;
case 'system_settings':
$machineQuery = Machine::query()->with(['company']);
if ($search) {
$machineQuery->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('serial_no', 'like', "%{$search}%");
});
}
$applyMachineFilters($machineQuery);
$machines = $machineQuery->latest()->paginate($per_page)->withQueryString();
break;
@ -76,15 +84,15 @@ class MachineSettingController extends AdminController
->orWhere('username', 'like', "%{$search}%");
});
}
if ($request->filled('company_id')) {
$userQuery->where('company_id', $request->company_id);
if ($currentUser->isSystemAdmin() && $companyId !== '') {
$userQuery->where('company_id', $companyId);
}
$users_list = $userQuery->latest()->paginate($per_page)->withQueryString();
break;
}
$companies = \App\Models\System\Company::select('id', 'name', 'code')->get();
$companies = Company::select('id', 'name', 'code')->orderBy('name')->get();
$tabView = match($tab) {
'models' => 'admin.basic-settings.machines.partials.tab-models',
@ -98,9 +106,14 @@ class MachineSettingController extends AdminController
}
// SSR 模式:一次查好全部三個 Tab 的首頁資料(供 x-show 即時切換)
$machines = Machine::query()
->with(['machineModel', 'paymentConfig', 'company'])
->latest()->paginate($per_page)->withQueryString();
$machineQuery = Machine::query()
->with(['machineModel', 'paymentConfig', 'company']);
if (in_array($tab, ['machines', 'system_settings'], true)) {
$applyMachineFilters($machineQuery);
}
$machines = $machineQuery->latest()->paginate($per_page)->withQueryString();
$models_list = MachineModel::query()
->withCount('machines')
@ -114,7 +127,7 @@ class MachineSettingController extends AdminController
// 基礎下拉資料 (用於新增/編輯機台的彈窗)
$models = MachineModel::select('id', 'name')->get();
$paymentConfigs = PaymentConfig::select('id', 'name')->get();
$companies = \App\Models\System\Company::select('id', 'name', 'code')->get();
$companies = Company::select('id', 'name', 'code')->orderBy('name')->get();
return view('admin.basic-settings.machines.index', compact(
'machines',

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin;
use App\Models\Machine\Machine;
use App\Models\System\Company;
use App\Services\Machine\MachineService;
use Illuminate\Http\Request;
use Illuminate\View\View;
@ -16,8 +17,10 @@ class MachineController extends AdminController
public function index(Request $request): View
{
$per_page = $request->input('per_page', 10);
$companyId = trim((string) $request->input('company_id', ''));
$query = Machine::query();
$currentUser = auth()->user();
// 搜尋:名稱或序號
if ($search = $request->input('search')) {
@ -27,13 +30,21 @@ class MachineController extends AdminController
});
}
if ($currentUser->isSystemAdmin() && $companyId !== '') {
$query->where('company_id', $companyId);
}
// 預加載統計資料
$machines = $query->orderBy("last_heartbeat_at", "desc")
->orderBy("id", "desc")
->paginate($per_page)
->withQueryString();
return view('admin.machines.index', compact('machines'));
$companies = $currentUser->isSystemAdmin()
? Company::select('id', 'name', 'code')->orderBy('name')->get()
: collect();
return view('admin.machines.index', compact('machines', 'companies'));
}
/**

View File

@ -5,9 +5,10 @@
tab: '{{ $tab }}',
tabLoading: false,
machineSearch: '',
machineCompanyId: @js(request('company_id')),
modelSearch: '',
permissionSearch: '',
permissionCompanyId: '{{ request('company_id') }}',
permissionCompanyId: @js(request('company_id')),
isUpdatingSetting: false,
async toggleSystemSetting(machineId, field, value) {
if (this.isUpdatingSetting) return;
@ -287,7 +288,10 @@
const search = searchMap[tabName] || '';
let qs = `tab=${tabName}&_ajax=1`;
if (search) qs += `&search=${encodeURIComponent(search)}`;
if (tabName === 'permissions' && this.permissionCompanyId) qs += `&company_id=${this.permissionCompanyId}`;
const machineCompanyId = (this.machineCompanyId || '').trim();
const permissionCompanyId = (this.permissionCompanyId || '').trim();
if ((tabName === 'machines' || tabName === 'system_settings') && machineCompanyId) qs += `&company_id=${machineCompanyId}`;
if (tabName === 'permissions' && permissionCompanyId) qs += `&company_id=${permissionCompanyId}`;
if (extraQuery) qs += extraQuery;
// 同步 URL不含 _ajax
@ -1669,4 +1673,4 @@
<script>
</script>
@endsection
@endsection

View File

@ -19,6 +19,14 @@
class="luxury-input py-2.5 pl-11 pr-4 block w-full sm:w-72 text-sm font-bold">
</div>
@if(auth()->user()->isSystemAdmin())
<div class="w-full sm:w-72">
<x-searchable-select id="machine-company-filter-machines" name="company_filter" :options="$companies" :selected="request('company_id')"
:placeholder="__('All Companies')"
x-on:change="machineCompanyId = $event.target.value; searchInTab('machines')" />
</div>
@endif
<div class="flex items-center gap-2 ml-auto sm:ml-0">
{{-- 搜尋按鈕 --}}
<button type="button" @click="searchInTab('machines')"
@ -31,7 +39,16 @@
{{-- 重置按鈕 --}}
<button type="button"
@click="machineSearch = ''; searchInTab('machines')"
@click="
machineSearch = '';
machineCompanyId = '';
const select = document.getElementById('machine-company-filter-machines');
if (select) {
select.value = ' ';
window.HSSelect?.getInstance(select)?.setValue(' ');
}
searchInTab('machines');
"
class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all active:scale-95"
title="{{ __('Reset') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
@ -268,5 +285,5 @@
</div>
<div class="mt-8 border-t border-slate-100/50 dark:border-slate-800/50 pt-6">
{{ $machines->appends(['tab' => 'machines'])->links('vendor.pagination.luxury') }}
{{ $machines->appends(['tab' => 'machines', 'search' => request('search'), 'company_id' => request('company_id')])->links('vendor.pagination.luxury') }}
</div>

View File

@ -15,17 +15,18 @@
class="luxury-input py-2.5 pl-11 pr-4 block w-full sm:w-72 text-sm font-bold">
</div>
@if(auth()->user()->isSystemAdmin())
<!-- Company Filter -->
<div class="w-full sm:w-72">
<x-searchable-select
x-model="permissionCompanyId"
@change="searchInTab('system_settings')"
:placeholder="__('所有公司')">
@foreach ($companies as $company)
<option value="{{ $company->id }}">{{ $company->name }}</option>
@endforeach
</x-searchable-select>
id="machine-company-filter-system-settings"
name="company_filter"
:options="$companies"
:selected="request('company_id')"
x-on:change="machineCompanyId = $event.target.value; searchInTab('system_settings')"
:placeholder="__('All Companies')" />
</div>
@endif
<div class="flex items-center gap-2 ml-auto sm:ml-0">
{{-- 搜尋按鈕 --}}
@ -39,7 +40,16 @@
{{-- 重置按鈕 --}}
<button type="button"
@click="machineSearch = ''; permissionCompanyId = ''; searchInTab('system_settings')"
@click="
machineSearch = '';
machineCompanyId = '';
const select = document.getElementById('machine-company-filter-system-settings');
if (select) {
select.value = ' ';
window.HSSelect?.getInstance(select)?.setValue(' ');
}
searchInTab('system_settings');
"
class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all active:scale-95"
title="{{ __('Reset') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">

View File

@ -438,10 +438,70 @@
:class="{ 'opacity-30 pointer-events-none transition-opacity duration-300': isLoadingTable }">
{{-- Filters --}}
<x-search-bar :action="route('admin.machines.index')" :value="request('search')"
:placeholder="__('Search machines...')">
<form method="GET" action="{{ route('admin.machines.index') }}"
class="flex flex-wrap items-center gap-3 sm:gap-4 mb-8"
@submit.prevent="
const url = new URL($el.action, window.location.origin);
new FormData($el).forEach((value, key) => {
if (value.trim()) url.searchParams.set(key, value);
else url.searchParams.delete(key);
});
fetchPage(url.pathname + url.search);
">
<input type="hidden" name="tab" value="list">
</x-search-bar>
<div class="relative group flex-1 sm:flex-none">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</span>
<input type="text" name="search" value="{{ request('search') }}"
placeholder="{{ __('Search machines...') }}"
class="luxury-input py-2.5 pl-11 pr-4 block w-full sm:w-72 text-sm font-bold">
</div>
@if(auth()->user()->isSystemAdmin())
<div class="w-full sm:w-72">
<x-searchable-select name="company_id" :options="$companies" :selected="request('company_id')"
:placeholder="__('All Companies')" />
</div>
@endif
<div class="flex items-center gap-2 ml-auto sm:ml-0">
<button type="submit"
class="p-2.5 rounded-xl bg-cyan-500 text-white hover:bg-cyan-600 shadow-lg shadow-cyan-500/25 transition-all active:scale-95"
title="{{ __('Search') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
<button type="button"
@click="
$el.closest('form').querySelectorAll('input[type=text]').forEach(i => i.value = '');
$el.closest('form').querySelectorAll('select').forEach(s => {
s.value = ' ';
const instance = window.HSSelect?.getInstance(s);
if (instance) instance.setValue(' ');
});
$el.closest('form').dispatchEvent(new Event('submit', { cancelable: true }));
"
class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all active:scale-95"
title="{{ __('Reset') }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
</button>
</div>
</form>
<!-- Desktop Table View -->
<div class="hidden xl:block overflow-x-auto pb-4">