This commit is contained in:
fahed
2026-02-18 14:13:45 +03:00
parent 0caef0b89a
commit 97a208734e
3 changed files with 85 additions and 16 deletions

View File

@@ -242,17 +242,46 @@ function Comparison({ data, showDataLabels, setShowDataLabels, includeVAT, setIn
return null; return null;
}; };
// Calculate capture rate and pilgrim data for quarters // Estimate pilgrims for any date range by prorating quarterly data
const estimatePilgrims = useCallback((start: string, end: string): number | null => {
const startDate = new Date(start);
const endDate = new Date(end);
let total = 0;
let hasData = false;
// Iterate through each quarter that overlaps with the range
const startYear = startDate.getFullYear();
const endYear = endDate.getFullYear();
for (let year = startYear; year <= endYear; year++) {
for (let q = 1; q <= 4; q++) {
const qStart = new Date(year, (q - 1) * 3, 1);
const qEnd = new Date(year, q * 3, 0); // last day of quarter
// Check overlap
if (qEnd < startDate || qStart > endDate) continue;
const pilgrims = umrahData[year]?.[q];
if (!pilgrims) continue;
// Calculate overlap fraction
const overlapStart = new Date(Math.max(qStart.getTime(), startDate.getTime()));
const overlapEnd = new Date(Math.min(qEnd.getTime(), endDate.getTime()));
const overlapDays = (overlapEnd.getTime() - overlapStart.getTime()) / (1000 * 60 * 60 * 24) + 1;
const quarterDays = (qEnd.getTime() - qStart.getTime()) / (1000 * 60 * 60 * 24) + 1;
total += pilgrims * (overlapDays / quarterDays);
hasData = true;
}
}
return hasData ? Math.round(total) : null;
}, []);
// Calculate capture rate and pilgrim data for any date range
const quarterData = useMemo(() => { const quarterData = useMemo(() => {
const prevYear = parseInt(ranges.prev.start.substring(0, 4)); const prevPilgrims = estimatePilgrims(ranges.prev.start, ranges.prev.end);
const currYear = parseInt(ranges.curr.start.substring(0, 4)); const currPilgrims = estimatePilgrims(ranges.curr.start, ranges.curr.end);
const prevQ = getQuarterFromRange(ranges.prev.start, ranges.prev.end);
const currQ = getQuarterFromRange(ranges.curr.start, ranges.curr.end);
if (!prevQ || !currQ) return null; // Only show for quarter comparisons
const prevPilgrims = umrahData[prevYear]?.[prevQ];
const currPilgrims = umrahData[currYear]?.[currQ];
if (!prevPilgrims && !currPilgrims) return null; if (!prevPilgrims && !currPilgrims) return null;
@@ -263,7 +292,7 @@ function Comparison({ data, showDataLabels, setShowDataLabels, includeVAT, setIn
pilgrims: { prev: prevPilgrims, curr: currPilgrims }, pilgrims: { prev: prevPilgrims, curr: currPilgrims },
captureRate: { prev: prevRate, curr: currRate } captureRate: { prev: prevRate, curr: currRate }
}; };
}, [ranges, prevMetrics.visitors, currMetrics.visitors]); }, [ranges, prevMetrics.visitors, currMetrics.visitors, estimatePilgrims]);
const captureRates = quarterData?.captureRate || null; const captureRates = quarterData?.captureRate || null;
const pilgrimCounts = quarterData?.pilgrims || null; const pilgrimCounts = quarterData?.pilgrims || null;

View File

@@ -1,4 +1,4 @@
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo, useEffect } from 'react';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { Line, Doughnut, Bar } from 'react-chartjs-2'; import { Line, Doughnut, Bar } from 'react-chartjs-2';
import { Carousel, EmptyState, FilterControls, StatCard } from './shared'; import { Carousel, EmptyState, FilterControls, StatCard } from './shared';
@@ -14,6 +14,7 @@ import {
groupByMuseum, groupByMuseum,
groupByDistrict, groupByDistrict,
umrahData, umrahData,
fetchPilgrimStats,
getUniqueYears, getUniqueYears,
getUniqueDistricts, getUniqueDistricts,
getDistrictMuseumMap, getDistrictMuseumMap,
@@ -32,6 +33,12 @@ const filterKeys = ['year', 'district', 'museum', 'quarter'];
function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setIncludeVAT }) { function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setIncludeVAT }) {
const { t } = useLanguage(); const { t } = useLanguage();
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const [pilgrimLoaded, setPilgrimLoaded] = useState(false);
// Fetch pilgrim stats from NocoDB on mount
useEffect(() => {
fetchPilgrimStats().then(() => setPilgrimLoaded(true));
}, []);
// Initialize filters from URL or defaults // Initialize filters from URL or defaults
const [filters, setFiltersState] = useState(() => { const [filters, setFiltersState] = useState(() => {

View File

@@ -24,7 +24,8 @@ const NOCODB_TOKEN = process.env.REACT_APP_NOCODB_TOKEN || '';
const NOCODB_TABLES = { const NOCODB_TABLES = {
districts: 'm8cup7lesbet0sa', districts: 'm8cup7lesbet0sa',
museums: 'm1c7od7mdirffvu', museums: 'm1c7od7mdirffvu',
dailyStats: 'mc7qhbdh3mjjwl8' dailyStats: 'mc7qhbdh3mjjwl8',
pilgrimStats: 'mmqgj0a0l5qxeqf'
}; };
// Cache keys // Cache keys
@@ -32,11 +33,43 @@ const CACHE_KEY = 'hihala_data_cache';
const CACHE_TIMESTAMP_KEY = 'hihala_data_cache_timestamp'; const CACHE_TIMESTAMP_KEY = 'hihala_data_cache_timestamp';
const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
export const umrahData: UmrahData = { // Default umrah data (overridden by NocoDB PilgrimStats when available)
2024: { 1: 11574494, 2: 10521465, 3: 3364627, 4: 7435625 }, export let umrahData: UmrahData = {
2025: { 1: 15222497, 2: 5443393, 3: null, 4: null } 2024: { 1: 15222497, 2: 10521465, 3: 6270868, 4: 7435625 },
2025: { 1: 15222497, 2: 5443393, 3: 26643148, 4: 31591871 }
}; };
// Fetch pilgrim stats from NocoDB and update umrahData
export async function fetchPilgrimStats(): Promise<UmrahData> {
try {
const url = `${NOCODB_URL}/api/v2/tables/${NOCODB_TABLES.pilgrimStats}/records?limit=50`;
const res = await fetch(url, { headers: { 'xc-token': NOCODB_TOKEN } });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
const records = json.list || [];
const data: UmrahData = { 2024: {}, 2025: {} };
for (const r of records) {
const year = r.Year as number;
const qStr = r.Quarter as string; // "Q1", "Q2", etc.
const qNum = parseInt(qStr.replace('Q', ''));
const total = r.TotalPilgrims as number;
if (year && qNum && total) {
if (!data[year]) data[year] = {};
data[year][qNum] = total;
}
}
// Update the global umrahData
umrahData = data;
console.log('PilgrimStats loaded from NocoDB:', data);
return data;
} catch (err) {
console.warn('Failed to fetch PilgrimStats, using defaults:', (err as Error).message);
return umrahData;
}
}
// ============================================ // ============================================
// Offline Cache Functions // Offline Cache Functions
// ============================================ // ============================================