cooper/filament-dcat-filters
A Filament plugin that adds DCAT-aware filters to your admin panels, making it easy to browse and narrow down data catalogs by metadata fields such as themes, formats, publishers, and access rights.
composer require and confirm you land on v2.1.2 — this patch changes date-range behavior, so pin the version if you have dashboard totals you can't afford to shift mid-sprint.RangeFilter in date mode. It is the single entry point that the rest of the filter/indicator surface delegates to, so understanding it unlocks the rest of the package.datetime column, e.g. RangeFilter::make('created_at')->dateColumn('created_at'), then render it as a date picker pair.dateColumn() → used by both query building and indicators (aggregations). Verify a filtered list and its indicator total agree.toTimestamp() → used when the range is handed off to a non-Eloquent consumer (cache keys, external API params, raw SQL).QueryBehaviorTest in particular doubles as executable documentation of expected bindings.Day-granularity dashboards. The v2.1.2 normalization is designed for exactly this: a user picks from=2024-01-01, to=2024-01-15 and expects all fifteen days inclusive. With the fix, from is aligned to 00:00:00 and to to 23:59:59, so created_at values at 09:30 on the 15th are now included. Use this mode whenever the UI is a date picker (no time component) regardless of whether the underlying column is date or datetime.
Keep one filter instance as the source of truth. Because dateColumn() feeds both the query path and the indicator path, build the filter once and reuse it:
$filter = RangeFilter::make('period')->dateColumn('created_at');
$rows = Order::query()->filter($filter)->paginate();
$total = Order::query()->filter($filter)->sum('total'); // same normalization
This is the pattern that prevents the classic "table shows 400 rows but the KPI tile says 397" bug — both paths traverse the same normalization logic.
Timestamps for downstream consumers. When the range leaves Eloquent (cache key, report job payload, third-party query API), route it through toTimestamp() rather than formatting dates by hand. That way the boundary alignment happens once and consistently. Store the resulting pair, not the raw strings.
Indicators over pre-aggregated tables. For indicator/aggregation queries, the from/to swap behavior means you no longer need defensive validation in the controller. Let the filter normalize from > to and keep your form request permissive; just surface a hint in the UI if you want to be helpful.
Testing pattern. Assert on bindings, not just result counts — that is precisely what the v2.1.2 test changes did (QueryBehaviorTest bindings expectations were corrected). Snapshot the generated whereBetween/>=/<= values so a future normalization tweak fails loudly instead of silently returning a different row count.
Extending. If you subclass RangeFilter for a domain-specific preset (e.g. "fiscal quarter"), override at the level where the date alignment happens and keep calling the parent for the boundary math. Don't re-implement 00:00:00/23:59:59 yourself — that's the logic that just changed.
<= '2024-01-15' now produce <= '2024-01-15 23:59:59'. Any report, export, or test asserting exact counts on a datetime column may legitimately gain rows on the boundary day. Re-run indicator tests after upgrading; the diff is expected, not a regression.date mode is affected. The note explicitly scopes the change to date-mode queries on datetime columns, the dateColumn() query/indicator paths, and toTimestamp(). If you pass an explicit Y-m-d H:i:s range, your boundaries are untouched — prefer that when you need precision.23:59:59 is not the last instant. If your column stores fractional seconds (datetime(6)), a row at 23:59:59.500000 still falls outside the range. If sub-second data matters, use explicit datetime bounds or a >= from AND < to+1day half-open query of your own.from > to swapping can hide UI bugs. The filter will happily swap a reversed range instead of erroring. That's convenient, but log or assert on it in development — otherwise a mis-wired date picker (e.g. swapped locale day/month parsing) goes unnoticed for months.00:00:00/23:59:59 happens in whatever timezone your values are interpreted in. If you store UTC but users pick dates in local time, convert before handing the range to the filter, or you'll be off by the offset at both boundaries.->toSql() + getBindings()); the normalized values live there, and the old-vs-new difference is immediately visible.2.1.x upgrades are free.How can I help you explore Laravel packages today?