Files
4ventures-client-portal/src/components/DateRangeContext.tsx
4Ventures cf27eba74d feat: KPIs con totales absolutos, backfill ads y ventas en cruce canal
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>
2026-09-28 10:23:36 +02:00

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;
}