import { StatsRange } from "../enums"; import { formatDateString, getHumanMonth, shiftDate } from "../Utils"; /** Vzhled bubliny s hodnotou — recharts ji kreslí inline stylem, ne přes CSS třídu. */ export const TOOLTIP_STYLE = { background: 'var(--ft-bg-card)', border: '1px solid var(--ft-border)', borderRadius: 'var(--ft-radius-sm)', color: 'var(--ft-text)', }; /** Hranice zvoleného období statistik, YYYY-MM-DD včetně. */ export type StatsBounds = { from: string, to: string }; /** * Popisek období na ose grafu — den u měsíce, měsíc u roku, rok u celého přehledu. * * @param period označení období ze serveru (YYYY-MM-DD, YYYY-MM nebo YYYY) * @param mode zvolený rozsah statistik */ export function formatPeriodLabel(period: string, mode: StatsRange): string { if (mode === StatsRange.MESIC) return formatDateString(period).slice(0, 5); if (mode === StatsRange.ROK) return getHumanMonth(period).split(' ')[0]; return period; } /** Vybere řadu součtů podle rozsahu — dny u měsíce, měsíce u roku, roky u celého přehledu. */ export function pickPeriods(stats: { byDay: T[], byMonth: T[], byYear: T[] }, mode: StatsRange): T[] { if (mode === StatsRange.MESIC) return stats.byDay; if (mode === StatsRange.ROK) return stats.byMonth; return stats.byYear; } /** * Vrátí označení všech období rozsahu, i těch bez dat — prázdný den v grafu * je informace sama o sobě. * * U "Vše" se začíná rokem prvního dne s daty, jinak by graf otevíralo deset let nul. * * @param range zobrazené období * @param mode rozsah statistik * @param firstDate první den s daty, YYYY-MM-DD; bez něj je u "Vše" výsledek prázdný */ export const listPeriods = (range: StatsBounds, mode: StatsRange, firstDate?: string): string[] => { if (mode === StatsRange.MESIC) { const periods: string[] = []; for (let date = range.from; date <= range.to; date = shiftDate(date, 1)) periods.push(date); return periods; } if (mode === StatsRange.ROK) { const year = range.from.substring(0, 4); return Array.from({ length: 12 }, (_, index) => `${year}-${String(index + 1).padStart(2, '0')}`); } if (!firstDate) return []; const first = Number(firstDate.substring(0, 4)); const last = Number(range.to.substring(0, 4)); return Array.from({ length: last - first + 1 }, (_, index) => String(first + index)); }; /** Zkrátí datum dne na označení období grafu. */ export const periodOf = (date: string, mode: StatsRange): string => { if (mode === StatsRange.MESIC) return date; if (mode === StatsRange.ROK) return date.substring(0, 7); return date.substring(0, 4); }; /** Kolik zdrojů má v koláči vlastní výseč; zbytek se slije do "Ostatní". */ const SOURCE_SLICE_COUNT = 7; /** Výseč koláče zdrojů. */ export type SourceSlice = { name: string, value: number, color: string }; /** * Sestaví výseče koláče dle zdroje — příjmu i útraty. Barva jde podle pořadí ve * výběru, zdrojů bývá hodně a pevná barva pro každý by paletu nestačila. * * @param bySource součty dle zdroje, sestupně dle zobrazované hodnoty * @param getValue zobrazovaná hodnota součtu (kcal, Kč) */ export const buildSourceSlices = ( bySource: T[], getValue: (total: T) => number, ): SourceSlice[] => { const slices = bySource .filter(total => getValue(total) > 0) .slice(0, SOURCE_SLICE_COUNT) .map((total, index) => ({ name: total.key, value: getValue(total), color: `var(--ft-series-${index + 1})` })); const rest = bySource.slice(SOURCE_SLICE_COUNT).reduce((sum, total) => sum + getValue(total), 0); if (rest > 0) slices.push({ name: 'Ostatní', value: rest, color: 'var(--ft-series-other)' }); return slices; };