enhancement
This commit is contained in:
85
src/components/shell/DateScope.tsx
Normal file
85
src/components/shell/DateScope.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { createContext, useContext, useMemo, useState, type ReactNode } from 'react';
|
||||
import type { DateRange } from '@/api/insights';
|
||||
import {
|
||||
DateRangePicker,
|
||||
presetRange,
|
||||
type RangePreset,
|
||||
} from '@/features/store-admin/DateRangePicker';
|
||||
|
||||
/**
|
||||
* The console's date filter, held once for the whole workspace.
|
||||
*
|
||||
* It sits in the top bar beside the profile, not on the page, and every page
|
||||
* reads it from here — the same relationship `BranchScope` already has to the
|
||||
* branch picker beside the logo. The two scopes now work the same way: the two
|
||||
* questions every page is asked, "which shop" and "when", are answered once in
|
||||
* the chrome rather than re-answered on each screen.
|
||||
*
|
||||
* ── What this changes about the pages ───────────────────────────────────────
|
||||
*
|
||||
* The range survives navigation. Setting March on Sales and clicking through to
|
||||
* Reports shows March, which is what somebody looking into a month actually
|
||||
* wants and is the whole reason for lifting it. It also means the live boards —
|
||||
* Console, Counters, Terminals — no longer force themselves back to today; they
|
||||
* follow the shared range like everything else. That is the trade the move
|
||||
* makes, and it is the right one, but it IS a change: those three used to be
|
||||
* pinned to the day whatever else you had chosen.
|
||||
*/
|
||||
|
||||
export interface DateScopeValue {
|
||||
preset: RangePreset;
|
||||
range: DateRange;
|
||||
set: (preset: RangePreset, range: DateRange) => void;
|
||||
/** Back to no filter at all — what an empty state offers as a way out. */
|
||||
clear: () => void;
|
||||
/** True when a range is actually narrowing the page. */
|
||||
isFiltered: boolean;
|
||||
}
|
||||
|
||||
/** Where the console starts. Month to date is what most pages want. */
|
||||
export const DEFAULT_RANGE_PRESET: RangePreset = 'month';
|
||||
|
||||
const DateScopeContext = createContext<DateScopeValue | null>(null);
|
||||
|
||||
export function DateScopeProvider({ children }: { children: ReactNode }) {
|
||||
const [preset, setPreset] = useState<RangePreset>(DEFAULT_RANGE_PRESET);
|
||||
const [range, setRange] = useState<DateRange>(() => presetRange(DEFAULT_RANGE_PRESET));
|
||||
|
||||
const value = useMemo<DateScopeValue>(
|
||||
() => ({
|
||||
preset,
|
||||
range,
|
||||
set: (nextPreset, nextRange) => {
|
||||
setPreset(nextPreset);
|
||||
setRange(nextRange);
|
||||
},
|
||||
clear: () => {
|
||||
setPreset('custom');
|
||||
setRange({});
|
||||
},
|
||||
isFiltered: Boolean(range.fromdate || range.todate),
|
||||
}),
|
||||
[preset, range],
|
||||
);
|
||||
|
||||
return <DateScopeContext.Provider value={value}>{children}</DateScopeContext.Provider>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The shared range.
|
||||
*
|
||||
* Throws outside the provider rather than inventing a local range: a page that
|
||||
* silently filtered on its own dates while the bar showed something else would
|
||||
* be the exact confusion this exists to remove.
|
||||
*/
|
||||
export function useDateScope(): DateScopeValue {
|
||||
const value = useContext(DateScopeContext);
|
||||
if (!value) throw new Error('useDateScope must be used inside a DateScopeProvider');
|
||||
return value;
|
||||
}
|
||||
|
||||
/** The control itself. Rendered once, in the top bar. */
|
||||
export function DateScopePicker() {
|
||||
const dates = useDateScope();
|
||||
return <DateRangePicker preset={dates.preset} range={dates.range} onChange={dates.set} />;
|
||||
}
|
||||
Reference in New Issue
Block a user