Comparativas WoW/MoM/YoY muestran prev/Δ además del %; sync ads admite histórico desde 2025; tabla ads↔CRM añade cierres, ventas y ROAS. Co-authored-by: Cursor <cursoragent@cursor.com>
120 lines
2.8 KiB
TypeScript
120 lines
2.8 KiB
TypeScript
'use client';
|
|
|
|
import React, {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
} from 'react';
|
|
import {
|
|
RangePreset,
|
|
resolveAdRange,
|
|
isoYesterday,
|
|
} from '@/lib/ad-tracker-range';
|
|
|
|
const STORAGE_KEY = '4v_global_date_range_v2';
|
|
|
|
type Stored = {
|
|
preset: RangePreset;
|
|
customSince: string;
|
|
customUntil: string;
|
|
};
|
|
|
|
type Ctx = {
|
|
preset: RangePreset;
|
|
customSince: string;
|
|
customUntil: string;
|
|
since: string;
|
|
until: string;
|
|
label: string;
|
|
setPreset: (p: RangePreset) => void;
|
|
setCustom: (since: string, until: string) => void;
|
|
};
|
|
|
|
const DateRangeContext = createContext<Ctx | null>(null);
|
|
|
|
function loadStored(): Stored {
|
|
const y = isoYesterday();
|
|
if (typeof window === 'undefined') {
|
|
return { preset: 'last_30d', customSince: y, customUntil: y };
|
|
}
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
if (!raw) return { preset: 'last_30d', customSince: y, customUntil: y };
|
|
const j = JSON.parse(raw) as Stored;
|
|
return {
|
|
preset: j.preset || 'last_30d',
|
|
customSince: j.customSince || y,
|
|
customUntil: j.customUntil || y,
|
|
};
|
|
} catch {
|
|
return { preset: 'last_30d', customSince: y, customUntil: y };
|
|
}
|
|
}
|
|
|
|
export function DateRangeProvider({ children }: { children: React.ReactNode }) {
|
|
const [preset, setPresetState] = useState<RangePreset>('last_30d');
|
|
const [customSince, setCustomSince] = useState('');
|
|
const [customUntil, setCustomUntil] = useState('');
|
|
const [hydrated, setHydrated] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const s = loadStored();
|
|
setPresetState(s.preset);
|
|
setCustomSince(s.customSince);
|
|
setCustomUntil(s.customUntil);
|
|
setHydrated(true);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!hydrated) return;
|
|
localStorage.setItem(
|
|
STORAGE_KEY,
|
|
JSON.stringify({ preset, customSince, customUntil })
|
|
);
|
|
}, [preset, customSince, customUntil, hydrated]);
|
|
|
|
const range = useMemo(
|
|
() =>
|
|
resolveAdRange(preset, {
|
|
since: customSince,
|
|
until: customUntil,
|
|
}),
|
|
[preset, customSince, customUntil]
|
|
);
|
|
|
|
const setPreset = useCallback((p: RangePreset) => setPresetState(p), []);
|
|
const setCustom = useCallback((since: string, until: string) => {
|
|
setCustomSince(since);
|
|
setCustomUntil(until);
|
|
setPresetState('custom');
|
|
}, []);
|
|
|
|
const value: Ctx = {
|
|
preset,
|
|
customSince: customSince || range.since,
|
|
customUntil: customUntil || range.until,
|
|
since: range.since,
|
|
until: range.until,
|
|
label: range.label,
|
|
setPreset,
|
|
setCustom,
|
|
};
|
|
|
|
return (
|
|
<DateRangeContext.Provider value={value}>
|
|
{children}
|
|
</DateRangeContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useDateRange(): Ctx {
|
|
const ctx = useContext(DateRangeContext);
|
|
if (!ctx) {
|
|
throw new Error('useDateRange must be used within DateRangeProvider');
|
|
}
|
|
return ctx;
|
|
}
|