Restore working state from f17e19f (before mobile overhaul)

Reverting all my changes that broke the desktop layout.
Starting fresh for mobile improvements.
This commit is contained in:
fahed
2026-02-03 15:29:03 +03:00
parent 222d583847
commit b2fcb16d12
9 changed files with 323 additions and 739 deletions

View File

@@ -1,7 +1,6 @@
import React, { useState, useMemo, useCallback } from 'react';
import { Line, Bar } from 'react-chartjs-2';
import { chartColors, createBaseOptions } from '../config/chartConfig';
import { useLanguage } from '../contexts/LanguageContext';
import {
filterDataByDateRange,
calculateMetrics,
@@ -13,21 +12,20 @@ import {
} from '../services/dataService';
import JSZip from 'jszip';
function Slides({ data }) {
const { t } = useLanguage();
const CHART_TYPES = useMemo(() => [
{ id: 'trend', label: t('slides.revenueTrend'), icon: '📈' },
{ id: 'museum-bar', label: t('slides.byMuseum'), icon: '📊' },
{ id: 'kpi-cards', label: t('slides.kpiSummary'), icon: '🎯' },
{ id: 'comparison', label: t('slides.yoyComparison'), icon: '⚖️' }
], [t]);
const CHART_TYPES = [
{ id: 'trend', label: 'Revenue Trend', icon: '📈' },
{ id: 'museum-bar', label: 'By Museum', icon: '📊' },
{ id: 'kpi-cards', label: 'KPI Summary', icon: '🎯' },
{ id: 'comparison', label: 'YoY Comparison', icon: '⚖️' }
];
const METRICS = useMemo(() => [
{ id: 'revenue', label: t('metrics.revenue'), field: 'revenue_incl_tax' },
{ id: 'visitors', label: t('metrics.visitors'), field: 'visits' },
{ id: 'tickets', label: t('metrics.tickets'), field: 'tickets' }
], [t]);
const METRICS = [
{ id: 'revenue', label: 'Revenue', field: 'revenue_incl_tax' },
{ id: 'visitors', label: 'Visitors', field: 'visits' },
{ id: 'tickets', label: 'Tickets', field: 'tickets' }
];
function Slides({ data }) {
const [slides, setSlides] = useState([]);
const [editingSlide, setEditingSlide] = useState(null);
const [previewMode, setPreviewMode] = useState(false);
@@ -173,7 +171,6 @@ ${generateChartScripts(slides, data, districts, districtMuseumMap)}
currentSlide={currentPreviewSlide}
setCurrentSlide={setCurrentPreviewSlide}
onExit={() => setPreviewMode(false)}
metrics={METRICS}
/>
);
}
@@ -181,8 +178,8 @@ ${generateChartScripts(slides, data, districts, districtMuseumMap)}
return (
<div className="slides-builder">
<div className="page-title">
<h1>{t('slides.title')}</h1>
<p>{t('slides.subtitle')}</p>
<h1>Presentation Builder</h1>
<p>Create slides with charts and export as HTML or PDF</p>
</div>
<div className="slides-toolbar">
@@ -190,7 +187,7 @@ ${generateChartScripts(slides, data, districts, districtMuseumMap)}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
</svg>
{t('slides.addSlide')}
Add Slide
</button>
{slides.length > 0 && (
<>
@@ -198,13 +195,13 @@ ${generateChartScripts(slides, data, districts, districtMuseumMap)}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>
</svg>
{t('slides.preview')}
Preview
</button>
<button className="btn-secondary" onClick={exportAsHTML}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
</svg>
{t('slides.exportHtml')}
Export HTML
</button>
</>
)}
@@ -212,11 +209,11 @@ ${generateChartScripts(slides, data, districts, districtMuseumMap)}
<div className="slides-workspace">
<div className="slides-list">
<h3>{t('slides.slidesCount')} ({slides.length})</h3>
<h3>Slides ({slides.length})</h3>
{slides.length === 0 ? (
<div className="empty-slides">
<p>{t('slides.noSlides')}</p>
<button onClick={addSlide}>{t('slides.addFirst')}</button>
<p>No slides yet</p>
<button onClick={addSlide}>Add your first slide</button>
</div>
) : (
<div className="slides-thumbnails">
@@ -248,8 +245,6 @@ ${generateChartScripts(slides, data, districts, districtMuseumMap)}
districts={districts}
districtMuseumMap={districtMuseumMap}
data={data}
chartTypes={CHART_TYPES}
metrics={METRICS}
/>
)}
</div>
@@ -257,8 +252,7 @@ ${generateChartScripts(slides, data, districts, districtMuseumMap)}
);
}
function SlideEditor({ slide, onUpdate, districts, districtMuseumMap, data, chartTypes, metrics }) {
const { t } = useLanguage();
function SlideEditor({ slide, onUpdate, districts, districtMuseumMap, data }) {
const availableMuseums = useMemo(() =>
getMuseumsForDistrict(districtMuseumMap, slide.district),
[districtMuseumMap, slide.district]
@@ -267,19 +261,19 @@ function SlideEditor({ slide, onUpdate, districts, districtMuseumMap, data, char
return (
<div className="slide-editor">
<div className="editor-section">
<label>{t('slides.slideTitle')}</label>
<label>Slide Title</label>
<input
type="text"
value={slide.title}
onChange={e => onUpdate({ title: e.target.value })}
placeholder={t('slides.slideTitle')}
placeholder="Enter slide title"
/>
</div>
<div className="editor-section">
<label>{t('slides.chartType')}</label>
<label>Chart Type</label>
<div className="chart-type-grid">
{chartTypes.map(type => (
{CHART_TYPES.map(type => (
<button
key={type.id}
className={`chart-type-btn ${slide.chartType === type.id ? 'active' : ''}`}
@@ -293,35 +287,35 @@ function SlideEditor({ slide, onUpdate, districts, districtMuseumMap, data, char
</div>
<div className="editor-section">
<label>{t('slides.metric')}</label>
<label>Metric</label>
<select value={slide.metric} onChange={e => onUpdate({ metric: e.target.value })}>
{metrics.map(m => <option key={m.id} value={m.id}>{m.label}</option>)}
{METRICS.map(m => <option key={m.id} value={m.id}>{m.label}</option>)}
</select>
</div>
<div className="editor-row">
<div className="editor-section">
<label>{t('slides.startDate')}</label>
<label>Start Date</label>
<input type="date" value={slide.startDate} onChange={e => onUpdate({ startDate: e.target.value })} />
</div>
<div className="editor-section">
<label>{t('slides.endDate')}</label>
<label>End Date</label>
<input type="date" value={slide.endDate} onChange={e => onUpdate({ endDate: e.target.value })} />
</div>
</div>
<div className="editor-row">
<div className="editor-section">
<label>{t('filters.district')}</label>
<label>District</label>
<select value={slide.district} onChange={e => onUpdate({ district: e.target.value, museum: 'all' })}>
<option value="all">{t('filters.allDistricts')}</option>
<option value="all">All Districts</option>
{districts.map(d => <option key={d} value={d}>{d}</option>)}
</select>
</div>
<div className="editor-section">
<label>{t('filters.museum')}</label>
<label>Museum</label>
<select value={slide.museum} onChange={e => onUpdate({ museum: e.target.value })}>
<option value="all">{t('filters.allMuseums')}</option>
<option value="all">All Museums</option>
{availableMuseums.map(m => <option key={m} value={m}>{m}</option>)}
</select>
</div>
@@ -335,28 +329,20 @@ function SlideEditor({ slide, onUpdate, districts, districtMuseumMap, data, char
checked={slide.showComparison}
onChange={e => onUpdate({ showComparison: e.target.checked })}
/>
{t('slides.showYoY')}
Show Year-over-Year Comparison
</label>
</div>
)}
<div className="slide-preview-box">
<h4>{t('slides.preview')}</h4>
<SlidePreview slide={slide} data={data} districts={districts} districtMuseumMap={districtMuseumMap} metrics={metrics} />
<h4>Preview</h4>
<SlidePreview slide={slide} data={data} districts={districts} districtMuseumMap={districtMuseumMap} />
</div>
</div>
);
}
// Static field mapping for charts (Chart.js labels don't need i18n)
const METRIC_FIELDS = {
revenue: { field: 'revenue_incl_tax', label: 'Revenue' },
visitors: { field: 'visits', label: 'Visitors' },
tickets: { field: 'tickets', label: 'Tickets' }
};
function SlidePreview({ slide, data, districts, districtMuseumMap, metrics }) {
const { t } = useLanguage();
function SlidePreview({ slide, data, districts, districtMuseumMap }) {
const filteredData = useMemo(() =>
filterDataByDateRange(data, slide.startDate, slide.endDate, {
district: slide.district,
@@ -365,7 +351,7 @@ function SlidePreview({ slide, data, districts, districtMuseumMap, metrics }) {
[data, slide.startDate, slide.endDate, slide.district, slide.museum]
);
const metricsData = useMemo(() => calculateMetrics(filteredData), [filteredData]);
const metrics = useMemo(() => calculateMetrics(filteredData), [filteredData]);
const baseOptions = useMemo(() => createBaseOptions(false), []);
const getMetricValue = useCallback((rows, metric) => {
@@ -383,11 +369,10 @@ function SlidePreview({ slide, data, districts, districtMuseumMap, metrics }) {
});
const sortedDates = Object.keys(grouped).sort();
const metricLabel = metrics?.find(m => m.id === slide.metric)?.label || METRIC_FIELDS[slide.metric]?.label || slide.metric;
return {
labels: sortedDates.map(d => d.substring(5)),
datasets: [{
label: metricLabel,
label: METRICS.find(m => m.id === slide.metric)?.label,
data: sortedDates.map(d => getMetricValue(grouped[d], slide.metric)),
borderColor: chartColors.primary,
backgroundColor: chartColors.primary + '20',
@@ -395,7 +380,7 @@ function SlidePreview({ slide, data, districts, districtMuseumMap, metrics }) {
tension: 0.4
}]
};
}, [filteredData, slide.metric, getMetricValue, metrics]);
}, [filteredData, slide.metric, getMetricValue]);
const museumData = useMemo(() => {
const byMuseum = {};
@@ -406,32 +391,31 @@ function SlidePreview({ slide, data, districts, districtMuseumMap, metrics }) {
});
const museums = Object.keys(byMuseum).sort();
const metricLabel = metrics?.find(m => m.id === slide.metric)?.label || METRIC_FIELDS[slide.metric]?.label || slide.metric;
return {
labels: museums,
datasets: [{
label: metricLabel,
label: METRICS.find(m => m.id === slide.metric)?.label,
data: museums.map(m => getMetricValue(byMuseum[m], slide.metric)),
backgroundColor: chartColors.primary,
borderRadius: 6
}]
};
}, [filteredData, slide.metric, getMetricValue, metrics]);
}, [filteredData, slide.metric, getMetricValue]);
if (slide.chartType === 'kpi-cards') {
return (
<div className="preview-kpis">
<div className="preview-kpi">
<div className="kpi-value">{formatCompactCurrency(metricsData.revenue)}</div>
<div className="kpi-label">{t('metrics.revenue')}</div>
<div className="kpi-value">{formatCompactCurrency(metrics.revenue)}</div>
<div className="kpi-label">Revenue</div>
</div>
<div className="preview-kpi">
<div className="kpi-value">{formatCompact(metricsData.visitors)}</div>
<div className="kpi-label">{t('metrics.visitors')}</div>
<div className="kpi-value">{formatCompact(metrics.visitors)}</div>
<div className="kpi-label">Visitors</div>
</div>
<div className="preview-kpi">
<div className="kpi-value">{formatCompact(metricsData.tickets)}</div>
<div className="kpi-label">{t('metrics.tickets')}</div>
<div className="kpi-value">{formatCompact(metrics.tickets)}</div>
<div className="kpi-label">Tickets</div>
</div>
</div>
);
@@ -452,8 +436,7 @@ function SlidePreview({ slide, data, districts, districtMuseumMap, metrics }) {
);
}
function PreviewMode({ slides, data, districts, districtMuseumMap, currentSlide, setCurrentSlide, onExit, metrics }) {
const { t } = useLanguage();
function PreviewMode({ slides, data, districts, districtMuseumMap, currentSlide, setCurrentSlide, onExit }) {
const handleKeyDown = useCallback((e) => {
if (e.key === 'ArrowRight' || e.key === ' ') {
setCurrentSlide(prev => Math.min(prev + 1, slides.length - 1));
@@ -476,7 +459,7 @@ function PreviewMode({ slides, data, districts, districtMuseumMap, currentSlide,
<div className="preview-slide">
<h1 className="preview-title">{slide?.title}</h1>
<div className="preview-content">
{slide && <SlidePreview slide={slide} data={data} districts={districts} districtMuseumMap={districtMuseumMap} metrics={metrics} />}
{slide && <SlidePreview slide={slide} data={data} districts={districts} districtMuseumMap={districtMuseumMap} />}
</div>
<div className="preview-footer">
<span>{currentSlide + 1} / {slides.length}</span>
@@ -485,7 +468,7 @@ function PreviewMode({ slides, data, districts, districtMuseumMap, currentSlide,
<div className="preview-controls">
<button onClick={() => setCurrentSlide(prev => Math.max(prev - 1, 0))} disabled={currentSlide === 0}></button>
<button onClick={() => setCurrentSlide(prev => Math.min(prev + 1, slides.length - 1))} disabled={currentSlide === slides.length - 1}></button>
<button onClick={onExit}>{t('slides.exit')}</button>
<button onClick={onExit}>Exit</button>
</div>
</div>
);