feat: add district filter (Hiraa/AsSaffiyah) from static mapping
- ETL writes District column to NocoDB DailySales - Museums mapped: Hiraa (Revelation, Holy Quraan, Trail, Makkah, VIP) AsSaffiyah (Creation Story, Best of Creation) - District filter added to Dashboard and Comparison (cascades to museum) - District Performance chart added (desktop + mobile) - Locale keys added for both EN and AR Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -128,6 +128,7 @@ async function fetchFromNocoDB(): Promise<MuseumRecord[]> {
|
||||
|
||||
return {
|
||||
date,
|
||||
district: row.District,
|
||||
museum_name: row.MuseumName,
|
||||
channel: row.Channel,
|
||||
visits: row.Visits,
|
||||
@@ -273,6 +274,7 @@ export async function refreshData(): Promise<FetchResult> {
|
||||
export function filterData(data: MuseumRecord[], filters: Filters): MuseumRecord[] {
|
||||
return data.filter(row => {
|
||||
if (filters.year && filters.year !== 'all' && row.year !== filters.year) return false;
|
||||
if (filters.district && filters.district !== 'all' && row.district !== filters.district) return false;
|
||||
if (filters.channel && filters.channel !== 'all' && row.channel !== filters.channel) return false;
|
||||
if (filters.museum && filters.museum !== 'all' && row.museum_name !== filters.museum) return false;
|
||||
if (filters.quarter && filters.quarter !== 'all' && row.quarter !== filters.quarter) return false;
|
||||
@@ -289,6 +291,7 @@ export function filterDataByDateRange(
|
||||
return data.filter(row => {
|
||||
if (!row.date) return false;
|
||||
if (row.date < startDate || row.date > endDate) return false;
|
||||
if (filters.district && filters.district !== 'all' && row.district !== filters.district) return false;
|
||||
if (filters.channel && filters.channel !== 'all' && row.channel !== filters.channel) return false;
|
||||
if (filters.museum && filters.museum !== 'all' && row.museum_name !== filters.museum) return false;
|
||||
return true;
|
||||
@@ -408,6 +411,28 @@ export function getUniqueYears(data: MuseumRecord[]): string[] {
|
||||
return years.sort((a, b) => parseInt(a) - parseInt(b));
|
||||
}
|
||||
|
||||
export function getUniqueDistricts(data: MuseumRecord[]): string[] {
|
||||
return [...new Set(data.map(r => r.district).filter(Boolean))].sort();
|
||||
}
|
||||
|
||||
export function groupByDistrict(data: MuseumRecord[], includeVAT: boolean = true): Record<string, GroupedData> {
|
||||
const revenueField = includeVAT ? 'revenue_gross' : 'revenue_net';
|
||||
const grouped: Record<string, GroupedData> = {};
|
||||
data.forEach(row => {
|
||||
if (!row.district) return;
|
||||
if (!grouped[row.district]) grouped[row.district] = { revenue: 0, visitors: 0, tickets: 0 };
|
||||
grouped[row.district].revenue += row[revenueField] || 0;
|
||||
grouped[row.district].visitors += row.visits || 0;
|
||||
grouped[row.district].tickets += row.tickets || 0;
|
||||
});
|
||||
return grouped;
|
||||
}
|
||||
|
||||
export function getMuseumsForDistrict(data: MuseumRecord[], district: string): string[] {
|
||||
if (district === 'all') return getUniqueMuseums(data);
|
||||
return [...new Set(data.filter(r => r.district === district).map(r => r.museum_name).filter(Boolean))].sort();
|
||||
}
|
||||
|
||||
export function getUniqueChannels(data: MuseumRecord[]): string[] {
|
||||
return [...new Set(data.map(r => r.channel).filter(Boolean))].sort();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user