CaiWuXiTong/resources/views/business/pages/courier-payables/courier-surcharge-settlement.blade.php

390 lines
28 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@php
$rows = $courierSurchargeSettlementRows ?? collect();
$filters = $courierSurchargeSettlementFilters ?? [];
$summary = $courierSurchargeSettlementSummary ?? ['count' => 0, 'settlement_amount' => 0];
$sync = $courierSurchargeSettlementSync ?? ['code' => 'unavailable', 'last_succeeded_at' => null];
$state = $courierSurchargeSettlementState ?? [];
$error = $courierSurchargeSettlementError ?? null;
$columns = [
['key' => 'courier_name', 'label' => '快递名称', 'fixed' => 'true'],
['key' => 'shipped_at', 'label' => '出库时间'],
['key' => 'waybill', 'label' => '单号'],
['key' => 'platform_product_id', 'label' => '平台商品ID'],
['key' => 'product_code', 'label' => '商品编码'],
['key' => 'shop_name', 'label' => '店铺名称'],
['key' => 'online_order_no', 'label' => '线上订单号'],
['key' => 'length', 'label' => '长'],
['key' => 'width', 'label' => '宽'],
['key' => 'height', 'label' => '高'],
['key' => 'volume', 'label' => '体积'],
['key' => 'inspection_type', 'label' => '查件类型'],
['key' => 'inspection_reason', 'label' => '查件原因'],
['key' => 'return_waybill_no', 'label' => '退回单号'],
['key' => 'settlement_weight', 'label' => '结算重量'],
['key' => 'settlement_amount', 'label' => '结算金额'],
['key' => 'surcharge_type', 'label' => '附加费类型', 'fixed' => 'end'],
];
$filterValue = static fn (string $key): string => trim((string) ($filters[$key] ?? ''));
$syncLabel = match ($sync['code'] ?? null) {
'fresh' => '最新',
'stale' => '稍有延迟',
'pending', 'initializing', 'syncing' => '同步中',
'failed', 'initial_failed' => '同步失败',
default => '不可用',
};
$emptyMessage = match (true) {
$error !== null => $error,
($sync['code'] ?? null) === 'pending' || ($sync['code'] ?? null) === 'syncing' => '镜像正在同步,完成后将自动显示附加费明细。',
($sync['code'] ?? null) === 'failed' || ($sync['code'] ?? null) === 'initial_failed' => '镜像同步失败,暂不展示不完整数据。',
default => '当前筛选条件没有匹配的附加费明细。',
};
$displayValue = static function (mixed $value, string $key): string {
if ($value === null || $value === '') {
return '—';
}
if ($key === 'settlement_amount' && is_numeric($value)) {
return number_format((float) $value, 2, '.', '');
}
return (string) $value;
};
@endphp
<section class="surcharge-settlement-page" aria-label="附加费结算">
<style>
.surcharge-settlement-page {
--settlement-ink: var(--vip-content-ink, #171717);
--settlement-muted: var(--vip-content-muted, #595959);
--settlement-accent: var(--vip-content-accent-dark, #856632);
--settlement-soft: var(--vip-content-accent-soft, #f7f0de);
--settlement-border: var(--vip-border, #d8d2c7);
display: grid;
gap: .65rem;
min-width: 0;
color: var(--settlement-ink);
font-family: "JetBrains Mono", "Microsoft YaHei", "PingFang SC", monospace;
font-size: .8125rem;
}
.surcharge-settlement-page *,
.surcharge-settlement-page *::before,
.surcharge-settlement-page *::after { box-sizing: border-box; }
.surcharge-settlement-page > * { min-width: 0; }
.settlement-notice {
display: flex;
align-items: flex-start;
gap: .65rem;
padding: .75rem 1rem;
border: 1px solid #d7c39d;
border-radius: .65rem;
background: #fffaf0;
color: #6a5129;
line-height: 1.55;
}
.settlement-notice .material-icons-outlined { color: var(--settlement-accent); font-size: 1.15rem; }
.settlement-notice strong { color: var(--settlement-ink); }
.settlement-summary {
display: grid;
grid-template-columns: 1.35fr repeat(3, minmax(8rem, 1fr));
overflow: hidden;
border: 1px solid var(--settlement-border);
border-radius: .75rem;
background: #fff;
}
.settlement-summary__intro,
.settlement-summary__metric { min-width: 0; padding: .55rem .9rem; }
.settlement-summary__intro { display: flex; align-items: center; gap: .6rem; }
.settlement-summary__icon { display: grid; flex: none; width: 1.9rem; height: 1.9rem; place-items: center; border-radius: .5rem; background: var(--settlement-soft); color: var(--settlement-accent); }
.settlement-summary__title { font-size: .875rem; font-weight: 700; }
.settlement-summary__subtitle { margin-top: .1rem; color: var(--settlement-muted); font-size: .68rem; }
.settlement-summary__metric { border-left: 1px solid var(--settlement-border); }
.settlement-summary__label { color: var(--settlement-muted); font-size: .68rem; }
.settlement-summary__value { margin-top: .15rem; font-size: .95rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.settlement-filter-bar {
display: grid;
grid-template-columns: repeat(4, minmax(10rem, 1fr));
gap: .45rem .6rem;
align-items: end;
padding: .55rem .9rem;
border: 1px solid var(--settlement-border);
border-radius: .75rem;
background: #fffdf8;
}
.settlement-field { display: grid; gap: .2rem; min-width: 0; }
.settlement-field[hidden] { display: none; }
.settlement-field label { color: var(--settlement-muted); font-size: .68rem; font-weight: 600; }
.settlement-field input,
.settlement-field select {
width: 100%;
height: 1.9rem;
min-width: 0;
padding: 0 .5rem;
border: 1px solid var(--settlement-border);
border-radius: .4rem;
background: #fff;
color: var(--settlement-ink);
font: inherit;
}
.settlement-field input:focus,
.settlement-field select:focus { border-color: var(--settlement-accent); outline: none; box-shadow: 0 0 0 3px rgba(133, 102, 50, .18); }
.settlement-actions { display: flex; flex-wrap: wrap; gap: .4rem; align-items: end; }
.settlement-filter-bar .settlement-actions { grid-column: 1 / -1; }
.settlement-button { display: inline-flex; align-items: center; justify-content: center; gap: .3rem; min-height: 1.9rem; padding: 0 .65rem; border: 1px solid var(--settlement-border); border-radius: .4rem; background: #fff; color: var(--settlement-ink); font: inherit; font-weight: 600; text-decoration: none; white-space: nowrap; cursor: pointer; }
.settlement-button--primary { border-color: var(--settlement-accent); background: var(--settlement-accent); color: #fff; }
.settlement-button .material-icons-outlined { font-size: .95rem; }
.settlement-import-bar { display: flex; flex-wrap: wrap; align-items: end; gap: .45rem .75rem; margin-top: .45rem; padding: .5rem .9rem; border: 1px solid var(--settlement-border); border-radius: .75rem; background: #fff; }
.settlement-import-bar .settlement-field { flex: 0 1 18rem; }
.settlement-import-bar input[type="file"] { width: 100%; height: 1.9rem; padding: .25rem .5rem; border: 1px dashed var(--settlement-border); border-radius: .4rem; background: #fffdf8; color: var(--settlement-ink); font: inherit; }
.settlement-import-bar .settlement-table-hint { align-self: center; flex-basis: 100%; }
.settlement-import-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .15rem .9rem; margin-left: auto; align-self: center; color: var(--settlement-muted); font-size: .7rem; }
.settlement-import-meta strong { color: var(--settlement-ink); }
.settlement-table-surface { overflow: visible; border: 1px solid var(--settlement-border); border-radius: .75rem; background: #fff; }
.settlement-table-head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: .4rem .9rem; padding: .5rem .9rem; border-bottom: 1px solid var(--settlement-border); }
.settlement-table-head .finance-table-controls { margin: 0; padding: 0; border: 0; background: transparent; }
.settlement-table-title { display: flex; align-items: center; gap: .45rem; font-size: .9rem; font-weight: 700; }
.settlement-table-title .material-icons-outlined { color: var(--settlement-accent); font-size: 1.1rem; }
.settlement-table-hint { color: var(--settlement-muted); font-size: .68rem; }
.settlement-table-scroll { overflow: auto; overscroll-behavior: contain; scrollbar-gutter: stable both-edges; }
.surcharge-settlement-page .settlement-table-scroll { max-height: max(14rem, min(calc(100vh - 12rem), 46rem)); }
.settlement-table { width: max-content; min-width: 100%; border-collapse: separate; border-spacing: 0; font-size: .75rem; }
.settlement-table th,
.settlement-table td { min-width: 7.5rem; padding: .62rem .7rem; border-right: 1px solid var(--settlement-border); border-bottom: 1px solid var(--settlement-border); text-align: left; vertical-align: top; white-space: nowrap; }
.settlement-table th { background: var(--settlement-soft); font-weight: 700; }
.settlement-table th[data-finance-fixed="true"],
.settlement-table td[data-finance-fixed="true"] { background: #fffdf8; }
.settlement-table th[data-finance-fixed="end"],
.settlement-table td[data-finance-fixed="end"] { background: #fffdf8; }
.settlement-table td { overflow: hidden; text-overflow: ellipsis; }
.settlement-table .settlement-empty td { min-width: 22rem; padding: 3rem 1rem; text-align: center; color: var(--settlement-muted); white-space: normal; }
.settlement-table tbody tr.is-clickable { cursor: pointer; }
.settlement-table tbody tr.is-clickable:not(.is-active):hover td,
.settlement-table tbody tr.is-clickable:not(.is-active):hover td[data-finance-fixed="true"],
.settlement-table tbody tr.is-clickable:not(.is-active):hover td[data-finance-fixed="end"] { background: #fcfaf5; }
.settlement-table tbody tr.is-active td,
.settlement-table tbody tr.is-active td[data-finance-fixed="true"],
.settlement-table tbody tr.is-active td[data-finance-fixed="end"] { background: var(--settlement-soft); border-bottom-color: #e4d7ba; }
.settlement-table tbody tr.is-active td:first-child { box-shadow: inset 3px 0 0 var(--settlement-accent); }
.settlement-footer { display: flex; align-items: center; justify-content: space-between; gap: .6rem; padding: .35rem .15rem 0; color: var(--settlement-muted); font-size: .72rem; }
.settlement-pagination { display: flex; align-items: center; gap: .35rem; }
.settlement-page-link { display: inline-flex; align-items: center; justify-content: center; min-width: 1.7rem; height: 1.6rem; border: 1px solid var(--settlement-border); border-radius: .4rem; background: #fff; color: var(--settlement-ink); text-decoration: none; }
.settlement-page-link:hover { border-color: var(--settlement-accent); background: var(--settlement-soft); }
.settlement-page-link.is-disabled { opacity: .4; pointer-events: none; }
.settlement-page-link .material-icons-outlined { font-size: 1rem; }
.settlement-filter-picker .finance-table-field-picker__panel { right: auto; left: 0; max-width: min(28rem, calc(100vw - 1rem)); }
@media (max-width: 900px) {
.settlement-summary { grid-template-columns: 1fr 1fr; }
.settlement-summary__intro { grid-column: 1 / -1; }
.settlement-summary__metric:nth-child(2) { border-left: 0; }
.settlement-filter-bar { grid-template-columns: repeat(2, minmax(10rem, 1fr)); }
}
@media (max-width: 560px) {
.settlement-summary { grid-template-columns: 1fr; }
.settlement-summary__metric { border-left: 0; border-top: 1px solid var(--settlement-border); }
.settlement-filter-bar { grid-template-columns: 1fr; }
}
</style>
@if (session('error'))
<div class="settlement-notice" role="status">
<span class="material-icons-outlined" aria-hidden="true">error_outline</span>
<div><strong>导入核验失败</strong><div>{{ session('error') }}</div></div>
</div>
@endif
@if ($error !== null || ($sync['code'] ?? null) !== 'fresh')
<div class="settlement-notice" role="status">
<span class="material-icons-outlined" aria-hidden="true">{{ $error !== null ? 'error_outline' : 'info' }}</span>
<div>
<strong>{{ $error !== null ? '附加费结算暂不可用' : '附加费结算数据状态' }}</strong>
<div>{{ $error ?? $emptyMessage }}</div>
@if (! empty($state['failure_stage']))
<small>失败阶段:{{ $state['failure_stage'] }}</small>
@endif
</div>
</div>
@endif
<div class="settlement-summary" aria-label="附加费结算概览">
<div class="settlement-summary__intro"><span class="settlement-summary__icon material-icons-outlined" aria-hidden="true">calculate</span><div><div class="settlement-summary__title">附加费结算</div><div class="settlement-summary__subtitle">结算标准读取附加费报价维护</div></div></div>
<div class="settlement-summary__metric"><div class="settlement-summary__label">筛选结果</div><div class="settlement-summary__value">{{ number_format((int) ($summary['count'] ?? $rows->total()), 0) }}</div></div>
<div class="settlement-summary__metric"><div class="settlement-summary__label">当前页结算金额</div><div class="settlement-summary__value">{{ number_format((float) ($summary['settlement_amount'] ?? 0), 2) }}</div></div>
<div class="settlement-summary__metric"><div class="settlement-summary__label">镜像状态</div><div class="settlement-summary__value">{{ $syncLabel }}</div></div>
</div>
<form class="settlement-filter-bar" method="GET" action="{{ route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-surcharge', 'tab' => 'settlement']) }}" aria-label="附加费结算筛选">
<div class="settlement-field" data-settlement-filter-field="courier_name"><label for="settlement-courier">快递名称</label><input id="settlement-courier" type="search" name="courier_name" value="{{ $filterValue('courier_name') }}" placeholder="输入快递名称"></div>
<div class="settlement-field" data-settlement-filter-field="waybill"><label for="settlement-waybill">单号</label><input id="settlement-waybill" type="search" name="waybill" value="{{ $filterValue('waybill') }}" placeholder="输入单号"></div>
<div class="settlement-field" data-settlement-filter-field="platform_product_id"><label for="settlement-platform-product">平台商品ID</label><input id="settlement-platform-product" type="search" name="platform_product_id" value="{{ $filterValue('platform_product_id') }}" placeholder="输入平台商品ID"></div>
<div class="settlement-field" data-settlement-filter-field="product_code"><label for="settlement-product-code">商品编码</label><input id="settlement-product-code" type="search" name="product_code" value="{{ $filterValue('product_code') }}" placeholder="输入商品编码"></div>
<div class="settlement-field" data-settlement-filter-field="shop_name"><label for="settlement-shop">店铺名称</label><input id="settlement-shop" type="search" name="shop_name" value="{{ $filterValue('shop_name') }}" placeholder="输入店铺名称"></div>
<div class="settlement-field" data-settlement-filter-field="online_order_no"><label for="settlement-online-order">线上订单号</label><input id="settlement-online-order" type="search" name="online_order_no" value="{{ $filterValue('online_order_no') }}" placeholder="输入线上订单号"></div>
<div class="settlement-field" data-settlement-filter-field="inspection_type"><label for="settlement-inspection">查件类型</label><input id="settlement-inspection" type="search" name="inspection_type" value="{{ $filterValue('inspection_type') }}" placeholder="一级选项"></div>
<div class="settlement-field" data-settlement-filter-field="inspection_reason"><label for="settlement-reason">查件原因</label><input id="settlement-reason" type="search" name="inspection_reason" value="{{ $filterValue('inspection_reason') }}" placeholder="二级选项"></div>
<div class="settlement-field" data-settlement-filter-field="surcharge_type"><label for="settlement-surcharge-type">附加费类型</label><select id="settlement-surcharge-type" name="surcharge_type"><option value="">全部附加费类型</option>@foreach (['退回费','拦截费','改地址','上楼费','送货费','超区费','安装费','保费','进仓费','超长费'] as $type)<option value="{{ $type }}" @selected($filterValue('surcharge_type') === $type)>{{ $type }}</option>@endforeach</select></div>
<div class="settlement-field" data-settlement-filter-field="status"><label for="settlement-status">结算状态</label><select id="settlement-status" name="status"><option value="">全部状态</option>@foreach (['未匹配报价','待核价','已计算'] as $status)<option value="{{ $status }}" @selected($filterValue('status') === $status)>{{ $status }}</option>@endforeach</select></div>
<div class="settlement-field" data-settlement-filter-field="created_from"><label for="settlement-created-from">创建日期起</label><input id="settlement-created-from" type="date" name="created_from" value="{{ $filterValue('created_from') }}"></div>
<div class="settlement-field" data-settlement-filter-field="created_to"><label for="settlement-created-to">创建日期止</label><input id="settlement-created-to" type="date" name="created_to" value="{{ $filterValue('created_to') }}"></div>
<div class="settlement-actions"><button class="settlement-button settlement-button--primary" type="submit"><span class="material-icons-outlined" aria-hidden="true">search</span>查询</button><a class="settlement-button" href="{{ route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-surcharge', 'tab' => 'settlement']) }}"><span class="material-icons-outlined" aria-hidden="true">refresh</span>重置</a><a class="settlement-button" href="{{ route('business.courier-surcharge.settlement.export', array_filter($filters, static fn ($value): bool => $value !== '' && $value !== null)) }}" title="按当前筛选导出附加费结算明细(XLSX,仅含已生成结算行的数据;未生成结算行的运单请用导入核验)"><span class="material-icons-outlined" aria-hidden="true">download</span>导出</a><div class="finance-table-field-picker settlement-filter-picker" data-settlement-filter-picker data-user="{{ auth()->id() }}"><button type="button" class="settlement-button" aria-expanded="false" title="选择要显示的筛选字段,选择会自动保存"><span class="material-icons-outlined" aria-hidden="true">tune</span>筛选设置</button><div class="finance-table-field-picker__panel" role="group" aria-label="选择筛选字段" hidden>@foreach (['courier_name' => '快递名称', 'waybill' => '单号', 'platform_product_id' => '平台商品ID', 'product_code' => '商品编码', 'shop_name' => '店铺名称', 'online_order_no' => '线上订单号', 'inspection_type' => '查件类型', 'inspection_reason' => '查件原因', 'surcharge_type' => '附加费类型', 'status' => '结算状态', 'created_from' => '创建日期起', 'created_to' => '创建日期止'] as $fieldKey => $fieldLabel)<label class="finance-table-field-picker__option"><input type="checkbox" value="{{ $fieldKey }}" data-settlement-filter-check checked><span>{{ $fieldLabel }}</span></label>@endforeach<div class="finance-table-field-picker__actions"><button type="button" data-settlement-filters-all>全选</button><button type="button" data-settlement-filters-reset>恢复默认</button></div></div></div></div>
</form>
<form class="settlement-import-bar" method="POST" action="{{ route('business.courier-surcharge.settlement.import') }}" enctype="multipart/form-data" aria-label="附加费结算导入核验">
@csrf
<div class="settlement-field">
<label for="settlement-import-file">导入核验(单号 + 附加费类型)</label>
<input id="settlement-import-file" type="file" name="file" accept=".xlsx,.xls,.csv" required>
</div>
<div class="settlement-actions">
<button class="settlement-button settlement-button--primary" type="submit"><span class="material-icons-outlined" aria-hidden="true">upload_file</span>导入并下载结果</button>
<a class="settlement-button" href="{{ route('business.courier-surcharge.settlement.import-template') }}"><span class="material-icons-outlined" aria-hidden="true">download</span>下载导入模板</a>
</div>
<div class="settlement-import-meta" role="status"><span>当前筛选结果:<strong>{{ number_format((int) ($summary['count'] ?? $rows->total()), 0) }}</strong> 条</span><span>数据状态:{{ $syncLabel }}@if (! empty($sync['last_succeeded_at'])),最近同步 {{ $sync['last_succeeded_at'] }}@endif</span></div>
<span class="settlement-table-hint">首行只需「单号」「附加费类型」:系统会按单号自动匹配快递,并按启用的报价核算应结金额;「金额」可选,填了会返回与系统的差异金额。</span>
</form>
<div class="settlement-table-surface" data-finance-table-surface>
<div class="settlement-table-head"><div><div class="settlement-table-title"><span class="material-icons-outlined" aria-hidden="true">table_view</span>附加费结算明细</div><div class="settlement-table-hint">快递名称和附加费类型固定显示,其余字段可在“列设置”中调整;点击行可高亮标记。</div></div><div class="finance-table-controls" data-finance-controls></div></div>
<div class="settlement-table-scroll finance-table-scroll-owner">
<table class="settlement-table" data-finance-table data-finance-table-name="courier-surcharge-settlement" data-finance-table-key="{{ auth()->id() }}:{{ company_id() }}:courier-payables:courier-surcharge:settlement:details" aria-label="附加费结算明细">
<thead><tr>
@foreach ($columns as $column)
<th scope="col" data-finance-column-key="{{ $column['key'] }}" data-finance-fixed="{{ $column['fixed'] ?? '' }}" data-export-field="{{ $column['key'] }}">{{ $column['label'] }}</th>
@endforeach
</tr></thead>
<tbody>
@forelse ($rows as $row)
<tr class="is-clickable" data-settlement-row>
@foreach ($columns as $column)
<td data-finance-column-key="{{ $column['key'] }}" data-export-value="{{ $displayValue(data_get($row, $column['key']), $column['key']) }}">{{ $displayValue(data_get($row, $column['key']), $column['key']) }}</td>
@endforeach
</tr>
@empty
<tr class="settlement-empty is-empty"><td colspan="{{ count($columns) }}"><span class="material-icons-outlined" aria-hidden="true">{{ $error !== null ? 'error_outline' : 'inbox' }}</span><div>{{ $emptyMessage }}</div></td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
@if (method_exists($rows, 'hasPages') && $rows->hasPages())
<div class="settlement-footer">
<span>第 {{ $rows->currentPage() }} / {{ $rows->lastPage() }} 页,本页 {{ $rows->count() }} 条,共 {{ number_format($rows->total()) }} 条</span>
<div class="settlement-pagination">
<a class="settlement-page-link {{ $rows->onFirstPage() ? 'is-disabled' : '' }}" href="{{ $rows->onFirstPage() ? '#' : $rows->url($rows->currentPage() - 1) }}" aria-label="上一页">
<span class="material-icons-outlined" aria-hidden="true">chevron_left</span>
</a>
<a class="settlement-page-link {{ $rows->hasMorePages() ? '' : 'is-disabled' }}" href="{{ $rows->hasMorePages() ? $rows->url($rows->currentPage() + 1) : '#' }}" aria-label="下一页">
<span class="material-icons-outlined" aria-hidden="true">chevron_right</span>
</a>
</div>
</div>
@endif
</section>
<script>
(() => {
const table = document.querySelector('table[data-finance-table-name="courier-surcharge-settlement"]');
if (!table) {
return;
}
const activate = (row) => {
table.querySelectorAll('tbody tr.is-active').forEach((item) => item.classList.remove('is-active'));
row.classList.add('is-active');
};
document.addEventListener('click', (event) => {
if (!(event.target instanceof Element) || event.target.closest('a, button, input, label, select')) {
return;
}
const row = event.target.closest('tr[data-settlement-row]');
if (row && table.contains(row)) {
event.preventDefault();
activate(row);
}
});
})();
(() => {
const filterBar = document.querySelector('.settlement-filter-bar');
const filterPicker = document.querySelector('[data-settlement-filter-picker]');
if (filterBar && filterPicker) {
const toggle = filterPicker.querySelector('button[aria-expanded]');
const panel = filterPicker.querySelector('.finance-table-field-picker__panel');
const checks = [...panel.querySelectorAll('input[data-settlement-filter-check]')];
const fields = [...filterBar.querySelectorAll('[data-settlement-filter-field]')];
const storageKey = 'caiwu:courier-surcharge-settlement:filter-fields:' + (filterPicker.dataset.user || 'anonymous');
const defaultKeys = checks.map((check) => check.value);
const apply = (selected, save) => {
fields.forEach((field) => {
const visible = selected.has(field.dataset.settlementFilterField);
field.hidden = !visible;
field.querySelectorAll('input, select').forEach((control) => {
if (visible) {
control.removeAttribute('disabled');
} else {
control.setAttribute('disabled', 'disabled');
}
});
});
checks.forEach((check) => { check.checked = selected.has(check.value); });
if (save) {
try { localStorage.setItem(storageKey, JSON.stringify([...selected])); } catch (_) {}
}
};
let stored = null;
try {
const parsed = JSON.parse(localStorage.getItem(storageKey) ?? 'null');
if (Array.isArray(parsed)) {
stored = new Set(parsed.filter((key) => defaultKeys.includes(key)));
}
} catch (_) {}
apply(stored ?? new Set(defaultKeys), false);
toggle.addEventListener('click', () => {
const open = panel.hidden;
panel.hidden = !open;
toggle.setAttribute('aria-expanded', String(open));
});
document.addEventListener('click', (event) => {
if (!panel.hidden && !filterPicker.contains(event.target)) {
panel.hidden = true;
toggle.setAttribute('aria-expanded', 'false');
}
});
checks.forEach((check) => check.addEventListener('change', () => {
apply(new Set(checks.filter((item) => item.checked).map((item) => item.value)), true);
}));
panel.querySelector('[data-settlement-filters-all]')?.addEventListener('click', () => {
apply(new Set(defaultKeys), true);
});
panel.querySelector('[data-settlement-filters-reset]')?.addEventListener('click', () => {
try { localStorage.removeItem(storageKey); } catch (_) {}
apply(new Set(defaultKeys), true);
});
}
})();
</script>