fix: improve campaign stats, shared chart components, and i18n coverage

- Extract shared chart constants to constants/charts.ts
- Add TREND_STYLES to stats/constants.ts for reuse
- Fix useChartColors with MutationObserver for theme changes
- Fix DailyChart date parsing to prevent timezone shift
- Add truncate to StatCard value for overflow safety
- Fix CampaignCard copy feedback inside try block
- Add NaN validation and staleTime to AdminCampaignStats
- Replace hardcoded strings with i18n keys in Referral page
- Add missing i18n keys across all 4 locales (en, ru, zh, fa)
- Add sales stats route to AdminPanel and App router
This commit is contained in:
Fringg
2026-03-02 20:34:24 +03:00
parent c7d05c4809
commit 673de08dd4
16 changed files with 449 additions and 84 deletions

View File

@@ -1,3 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
import { rgbToHex } from '../utils/colorConversion';
/** CSS variable names for chart theme colors */
@@ -48,16 +50,35 @@ export interface ChartColors {
* Returns hex values derived from the current theme's CSS variables,
* adapting to light/dark mode and admin-customized accent colors.
*
* No memoization — getComputedStyle is cheap and themes can change at any time.
* Colors are memoized and only recomputed when the document element's
* class attribute changes (i.e. theme switch).
*/
export function useChartColors(): ChartColors {
return {
earnings: cssVarToHex(CSS_VARS.earnings, FALLBACK.earnings),
referrals: cssVarToHex(CSS_VARS.referrals, FALLBACK.referrals),
grid: cssVarToHex(CSS_VARS.grid, FALLBACK.grid),
tooltipBg: cssVarToHex(CSS_VARS.tooltipBg, FALLBACK.tooltipBg),
tooltipBorder: cssVarToHex(CSS_VARS.tooltipBorder, FALLBACK.tooltipBorder),
tick: cssVarToHex(CSS_VARS.tick, FALLBACK.tick),
label: cssVarToHex(CSS_VARS.label, FALLBACK.label),
};
const [themeKey, setThemeKey] = useState(() =>
typeof document !== 'undefined' ? document.documentElement.className : '',
);
useEffect(() => {
const observer = new MutationObserver(() => {
setThemeKey(document.documentElement.className);
});
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
return () => observer.disconnect();
}, []);
const colors = useMemo<ChartColors>(
() => ({
earnings: cssVarToHex(CSS_VARS.earnings, FALLBACK.earnings),
referrals: cssVarToHex(CSS_VARS.referrals, FALLBACK.referrals),
grid: cssVarToHex(CSS_VARS.grid, FALLBACK.grid),
tooltipBg: cssVarToHex(CSS_VARS.tooltipBg, FALLBACK.tooltipBg),
tooltipBorder: cssVarToHex(CSS_VARS.tooltipBorder, FALLBACK.tooltipBorder),
tick: cssVarToHex(CSS_VARS.tick, FALLBACK.tick),
label: cssVarToHex(CSS_VARS.label, FALLBACK.label),
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[themeKey],
);
return colors;
}