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:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user