refactor: extract shared locale, date helpers, and components (H6)
~300 lines of code that were independently duplicated in Dashboard.tsx and Comparison.tsx are now in shared modules: - src/lib/locale.ts — LC interface, EN and AR language configs (merged fields from both pages into one unified interface) - src/lib/dateHelpers.ts — MONTH_KEYS, isLeap, makePresets, guessPreset, periodNameL, dateRangeTextL, currentMonth, shiftYear - src/components/shared/PeriodPicker.tsx — InlinePicker + PeriodHero - src/components/shared/AltMultiSelect.tsx — AltMultiSelect - src/components/shared/MetricCard.tsx — MetricCard Dashboard.tsx and Comparison.tsx now import from these shared modules. Zero behavioral changes — all props, ARIA, and render output unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
// ─── language config ──────────────────────────────────────────────
|
||||
// Shared LC interface used by Dashboard and Comparison.
|
||||
// Fields marked with a comment are only consumed by one page but kept
|
||||
// here so both components share a single type.
|
||||
export interface LC {
|
||||
dir: 'ltr' | 'rtl';
|
||||
/** @deprecated Fonts are now loaded from index.html; kept for compatibility */
|
||||
fontImport: string;
|
||||
bodyFont: string;
|
||||
displayFont: string;
|
||||
monoFont: string;
|
||||
monthFull: string[];
|
||||
monthShort: string[];
|
||||
periods: Record<string, string>;
|
||||
fullYearLabel: (y: number) => string;
|
||||
dateRangeSep: string;
|
||||
backLink: string;
|
||||
backTo: string;
|
||||
pageTitle: string;
|
||||
pageSub: string;
|
||||
// Dashboard
|
||||
changePeriod: string;
|
||||
close: string;
|
||||
apply: string;
|
||||
filter: string;
|
||||
allDistricts: string;
|
||||
allChannels: string;
|
||||
allMuseums: string;
|
||||
countDistricts: (n: number) => string;
|
||||
countChannels: (n: number) => string;
|
||||
countMuseums: (n: number) => string;
|
||||
reset: string;
|
||||
exclVAT: string;
|
||||
inclVAT: string;
|
||||
keyMetrics: string;
|
||||
revenue: string;
|
||||
visitors: string;
|
||||
tickets: string;
|
||||
avgRev: string;
|
||||
pilgrims: string;
|
||||
captureRate: string;
|
||||
charts: string;
|
||||
trendTitle: string;
|
||||
museumTitle: string;
|
||||
channelTitle: string;
|
||||
districtTitle: string;
|
||||
daily: string;
|
||||
weekly: string;
|
||||
monthly: string;
|
||||
newLabel: string;
|
||||
clearSel: string;
|
||||
monthSection: string;
|
||||
periodSection: string;
|
||||
from: string;
|
||||
to: string;
|
||||
vsLabel: string;
|
||||
barLabel: string;
|
||||
pieLabel: string;
|
||||
absLabel: string;
|
||||
pctLabel: string;
|
||||
// Comparison-specific
|
||||
currentRole: string;
|
||||
previousRole: string;
|
||||
currentHint: string;
|
||||
previousHint: string;
|
||||
vs: string;
|
||||
}
|
||||
|
||||
export const EN: LC = {
|
||||
dir: 'ltr',
|
||||
fontImport: `@import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=Outfit:wght@300;400;500;600;700&display=swap');`,
|
||||
bodyFont: "'Outfit', sans-serif",
|
||||
displayFont: "'DM Serif Display', serif",
|
||||
monoFont: "ui-monospace, 'Cascadia Code', monospace",
|
||||
monthFull: ['January','February','March','April','May','June','July','August','September','October','November','December'],
|
||||
monthShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
|
||||
periods: { q1:'Q1', q2:'Q2', q3:'Q3', q4:'Q4', h1:'H1', h2:'H2', full:'Full Year' },
|
||||
fullYearLabel: (y) => String(y),
|
||||
dateRangeSep: '→',
|
||||
backLink: 'Back to Dashboard', backTo: '/',
|
||||
pageTitle: 'Overview', pageSub: 'Museum performance at a glance.',
|
||||
changePeriod: 'Change period', close: 'Cancel', apply: 'Apply',
|
||||
filter: 'Filter',
|
||||
allDistricts: 'All districts', allChannels: 'All channels', allMuseums: 'All museums',
|
||||
countDistricts: (n) => `${n} districts`,
|
||||
countChannels: (n) => `${n} channels`,
|
||||
countMuseums: (n) => `${n} museums`,
|
||||
reset: 'Reset', exclVAT: 'Excl. VAT', inclVAT: 'Incl. VAT',
|
||||
keyMetrics: 'Key Metrics',
|
||||
revenue: 'Revenue', visitors: 'Visitors', tickets: 'Tickets',
|
||||
avgRev: 'Avg Rev / Visitor', pilgrims: 'Pilgrims', captureRate: 'Capture Rate %',
|
||||
charts: 'Charts',
|
||||
trendTitle: 'Trend over time', museumTitle: 'By museum',
|
||||
channelTitle: 'By channel', districtTitle: 'By district',
|
||||
daily: 'Daily', weekly: 'Weekly', monthly: 'Monthly',
|
||||
newLabel: 'New', clearSel: 'Clear selection',
|
||||
monthSection: 'Month', periodSection: 'Quarter · Half · Year',
|
||||
from: 'From', to: 'To', vsLabel: 'vs',
|
||||
barLabel: 'Bar', pieLabel: 'Pie', absLabel: '#', pctLabel: '%',
|
||||
currentRole: 'This period', previousRole: 'Compared to',
|
||||
currentHint: 'primary', previousHint: 'auto year −1',
|
||||
vs: 'vs',
|
||||
};
|
||||
|
||||
export const AR: LC = {
|
||||
dir: 'rtl',
|
||||
fontImport: `@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@300;400;500;600;700&display=swap');`,
|
||||
bodyFont: "'IBM Plex Sans Arabic', sans-serif",
|
||||
displayFont: "'IBM Plex Sans Arabic', sans-serif",
|
||||
monoFont: "'IBM Plex Sans Arabic', sans-serif",
|
||||
monthFull: ['يناير','فبراير','مارس','أبريل','مايو','يونيو','يوليو','أغسطس','سبتمبر','أكتوبر','نوفمبر','ديسمبر'],
|
||||
monthShort: ['ينا','فبر','مار','أبر','ماي','يون','يول','أغس','سبت','أكت','نوف','ديس'],
|
||||
periods: { q1:'ر١', q2:'ر٢', q3:'ر٣', q4:'ر٤', h1:'ن١', h2:'ن٢', full:'السنة' },
|
||||
fullYearLabel: (y) => `${y} كاملاً`,
|
||||
dateRangeSep: '–',
|
||||
backLink: 'العودة إلى لوحة التحكم', backTo: '/ar',
|
||||
pageTitle: 'نظرة عامة', pageSub: 'أداء المتاحف في لمحة.',
|
||||
changePeriod: 'تغيير الفترة', close: 'إلغاء', apply: 'تطبيق',
|
||||
filter: 'تصفية',
|
||||
allDistricts: 'كل المناطق', allChannels: 'كل القنوات', allMuseums: 'كل المتاحف',
|
||||
countDistricts: (n) => `${n} مناطق`,
|
||||
countChannels: (n) => `${n} قنوات`,
|
||||
countMuseums: (n) => `${n} متاحف`,
|
||||
reset: 'إعادة ضبط', exclVAT: 'بدون ضريبة', inclVAT: 'مع ضريبة',
|
||||
keyMetrics: 'المؤشرات الرئيسية',
|
||||
revenue: 'الإيرادات', visitors: 'الزوار', tickets: 'التذاكر',
|
||||
avgRev: 'متوسط الإيراد / زائر', pilgrims: 'الحجاج والمعتمرون', captureRate: 'معدل الاستيعاب %',
|
||||
charts: 'المخططات',
|
||||
trendTitle: 'الاتجاه عبر الزمن', museumTitle: 'حسب المتحف',
|
||||
channelTitle: 'حسب القناة', districtTitle: 'حسب المنطقة',
|
||||
daily: 'يومي', weekly: 'أسبوعي', monthly: 'شهري',
|
||||
newLabel: 'جديد', clearSel: 'مسح التحديد',
|
||||
monthSection: 'الشهر', periodSection: 'ربع · نصف · سنة',
|
||||
from: 'من', to: 'إلى', vsLabel: 'مقابل',
|
||||
barLabel: 'أعمدة', pieLabel: 'دائرة', absLabel: '#', pctLabel: '%',
|
||||
currentRole: 'الفترة الحالية', previousRole: 'مقارنةً بـ',
|
||||
currentHint: 'رئيسية', previousHint: 'تلقائياً −١ سنة',
|
||||
vs: 'مقابل',
|
||||
};
|
||||
Reference in New Issue
Block a user