feat(reports): parseOperationalFilters pure parser (Area scope)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
27
src/lib/services/reports/operational-filters.ts
Normal file
27
src/lib/services/reports/operational-filters.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Operational report filters. Mirrors `sales-filters.ts`: the parser is a
|
||||||
|
* pure, unit-testable function so the route just hands it the query params.
|
||||||
|
*
|
||||||
|
* Beta scope is Area only (a berth-area scope). The shape is intentionally
|
||||||
|
* an object so a Status dimension can be added later without a rename.
|
||||||
|
*/
|
||||||
|
export interface OperationalFilters {
|
||||||
|
areas?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the `area` CSV query param into a free list of port-defined area
|
||||||
|
* strings. Empty / whitespace entries are dropped. Drizzle parameterises
|
||||||
|
* the downstream `inArray`, so unvalidated values are injection-safe.
|
||||||
|
* Returns `undefined` when no areas are active (→ no filter).
|
||||||
|
*/
|
||||||
|
export function parseOperationalFilters(params: URLSearchParams): OperationalFilters | undefined {
|
||||||
|
const raw = params.get('area');
|
||||||
|
if (!raw) return undefined;
|
||||||
|
const areas = raw
|
||||||
|
.split(',')
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter((s) => s.length > 0);
|
||||||
|
if (areas.length === 0) return undefined;
|
||||||
|
return { areas };
|
||||||
|
}
|
||||||
29
tests/unit/services/reports/operational-filters.test.ts
Normal file
29
tests/unit/services/reports/operational-filters.test.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { parseOperationalFilters } from '@/lib/services/reports/operational-filters';
|
||||||
|
|
||||||
|
function params(qs: string): URLSearchParams {
|
||||||
|
return new URLSearchParams(qs);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('parseOperationalFilters', () => {
|
||||||
|
it('returns undefined when no area param is present', () => {
|
||||||
|
expect(parseOperationalFilters(params(''))).toBeUndefined();
|
||||||
|
expect(parseOperationalFilters(params('from=x&to=y'))).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses a single area', () => {
|
||||||
|
expect(parseOperationalFilters(params('area=A'))).toEqual({ areas: ['A'] });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses a CSV of areas and trims whitespace', () => {
|
||||||
|
expect(parseOperationalFilters(params('area=A,%20B%20,C'))).toEqual({
|
||||||
|
areas: ['A', 'B', 'C'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drops empty / whitespace-only entries, returning undefined when nothing is left', () => {
|
||||||
|
expect(parseOperationalFilters(params('area=%20,%20'))).toBeUndefined();
|
||||||
|
expect(parseOperationalFilters(params('area='))).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user