171 lines
6.8 KiB
TypeScript
171 lines
6.8 KiB
TypeScript
import React, { useRef } from 'react';
|
|
import AltMultiSelect from '../shared/AltMultiSelect';
|
|
import type { ReportConfig } from './reportHelpers';
|
|
|
|
interface Props {
|
|
config: ReportConfig;
|
|
onChange: (patch: Partial<ReportConfig>) => void;
|
|
allMuseums: string[];
|
|
allChannels: string[];
|
|
}
|
|
|
|
function SectionTitle({ children }: { children: React.ReactNode }) {
|
|
return <div className="rf-section-title">{children}</div>;
|
|
}
|
|
|
|
function Field({ label, children }: { label: string; children: React.ReactNode }) {
|
|
return (
|
|
<label className="rf-field">
|
|
<span className="rf-label">{label}</span>
|
|
{children}
|
|
</label>
|
|
);
|
|
}
|
|
|
|
function Toggle({ left, right, value, onChange }: {
|
|
left: string; right: string; value: boolean; onChange: (v: boolean) => void;
|
|
}) {
|
|
return (
|
|
<div className="rf-toggle">
|
|
<button type="button" className={`rf-toggle-opt${!value ? ' rf-toggle-opt--on' : ''}`} onClick={() => onChange(false)}>{left}</button>
|
|
<button type="button" className={`rf-toggle-opt${value ? ' rf-toggle-opt--on' : ''}`} onClick={() => onChange(true)}>{right}</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function CheckRow({ label, checked, onChange }: { label: string; checked: boolean; onChange: (v: boolean) => void }) {
|
|
return (
|
|
<label className="rf-check-row">
|
|
<input type="checkbox" checked={checked} onChange={e => onChange(e.target.checked)} className="rf-checkbox" />
|
|
<span>{label}</span>
|
|
</label>
|
|
);
|
|
}
|
|
|
|
export default function ReportForm({ config: cfg, onChange, allMuseums, allChannels }: Props) {
|
|
const logoInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
const handleLogoUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
if (!file) return;
|
|
if (file.size > 2 * 1024 * 1024) { alert('Logo must be under 2 MB'); return; }
|
|
const reader = new FileReader();
|
|
reader.onload = () => onChange({ clientLogoBase64: reader.result as string });
|
|
reader.readAsDataURL(file);
|
|
};
|
|
|
|
return (
|
|
<div className="report-form">
|
|
|
|
<SectionTitle>Client Info</SectionTitle>
|
|
|
|
<Field label="Report title">
|
|
<input className="rf-input" type="text" value={cfg.title}
|
|
onChange={e => onChange({ title: e.target.value })}
|
|
placeholder="Q1 2025 Visitor Performance" />
|
|
</Field>
|
|
|
|
<Field label="Prepared for (company)">
|
|
<input className="rf-input" type="text" value={cfg.clientName}
|
|
onChange={e => onChange({ clientName: e.target.value })}
|
|
placeholder="Acme Group" />
|
|
</Field>
|
|
|
|
<Field label="Contact name (optional)">
|
|
<input className="rf-input" type="text" value={cfg.contactName}
|
|
onChange={e => onChange({ contactName: e.target.value })}
|
|
placeholder="Mohammed Al-..." />
|
|
</Field>
|
|
|
|
<Field label="Client logo (PNG/JPG, max 2 MB)">
|
|
<div className="rf-logo-row">
|
|
<button type="button" className="rf-upload-btn" onClick={() => logoInputRef.current?.click()}>
|
|
{cfg.clientLogoBase64 ? 'Change logo' : 'Upload logo'}
|
|
</button>
|
|
{cfg.clientLogoBase64 && (
|
|
<>
|
|
<img src={cfg.clientLogoBase64} alt="preview" className="rf-logo-preview" />
|
|
<button type="button" className="rf-remove-btn" onClick={() => onChange({ clientLogoBase64: null })}>✕</button>
|
|
</>
|
|
)}
|
|
<input ref={logoInputRef} type="file" accept="image/png,image/jpeg"
|
|
style={{ display: 'none' }} onChange={handleLogoUpload} />
|
|
</div>
|
|
</Field>
|
|
|
|
<Field label="Accent color">
|
|
<div className="rf-color-row">
|
|
<input type="color" value={cfg.accentColor}
|
|
onChange={e => onChange({ accentColor: e.target.value })}
|
|
className="rf-color-input" />
|
|
<span className="rf-color-val">{cfg.accentColor}</span>
|
|
</div>
|
|
</Field>
|
|
|
|
<SectionTitle>Data Selection</SectionTitle>
|
|
|
|
<div className="rf-date-row">
|
|
<Field label="Start date">
|
|
<input className="rf-input" type="date" value={cfg.startDate}
|
|
onChange={e => onChange({ startDate: e.target.value })} />
|
|
</Field>
|
|
<Field label="End date">
|
|
<input className="rf-input" type="date" value={cfg.endDate}
|
|
onChange={e => onChange({ endDate: e.target.value })} />
|
|
</Field>
|
|
</div>
|
|
|
|
<Field label="Museums">
|
|
<AltMultiSelect value={cfg.selectedMuseums} options={allMuseums}
|
|
onChange={v => onChange({ selectedMuseums: v })}
|
|
allLabel="All museums" countLabel={n => `${n} museums`} clearLabel="Clear" />
|
|
</Field>
|
|
|
|
<Field label="Channels">
|
|
<AltMultiSelect value={cfg.selectedChannels} options={allChannels}
|
|
onChange={v => onChange({ selectedChannels: v })}
|
|
allLabel="All channels" countLabel={n => `${n} channels`} clearLabel="Clear" />
|
|
</Field>
|
|
|
|
<Field label="VAT">
|
|
<Toggle left="Excl. VAT" right="Incl. VAT" value={cfg.includeVAT}
|
|
onChange={v => onChange({ includeVAT: v })} />
|
|
</Field>
|
|
|
|
<CheckRow label="Include previous year comparison"
|
|
checked={cfg.includeComparison} onChange={v => onChange({ includeComparison: v })} />
|
|
|
|
<SectionTitle>Content Sections</SectionTitle>
|
|
|
|
<CheckRow label="Executive summary" checked={cfg.showExecutiveSummary} onChange={v => onChange({ showExecutiveSummary: v })} />
|
|
<CheckRow label="Key metrics table" checked={cfg.showMetricsTable} onChange={v => onChange({ showMetricsTable: v })} />
|
|
<CheckRow label="Revenue trend chart" checked={cfg.showTrendChart} onChange={v => onChange({ showTrendChart: v })} />
|
|
<CheckRow label="Breakdown by museum" checked={cfg.showMuseumBreakdown} onChange={v => onChange({ showMuseumBreakdown: v })} />
|
|
<CheckRow label="Breakdown by channel" checked={cfg.showChannelBreakdown} onChange={v => onChange({ showChannelBreakdown: v })} />
|
|
<CheckRow label="Pilgrim capture rate" checked={cfg.showPilgrimCapture} onChange={v => onChange({ showPilgrimCapture: v })} />
|
|
|
|
<SectionTitle>Presentation</SectionTitle>
|
|
|
|
<Field label="Language">
|
|
<Toggle left="English" right="العربية" value={cfg.language === 'ar'}
|
|
onChange={v => onChange({ language: v ? 'ar' : 'en' })} />
|
|
</Field>
|
|
|
|
<Field label="Orientation">
|
|
<Toggle left="Portrait" right="Landscape" value={cfg.orientation === 'landscape'}
|
|
onChange={v => onChange({ orientation: v ? 'landscape' : 'portrait' })} />
|
|
</Field>
|
|
|
|
<Field label="Confidentiality">
|
|
<select className="rf-input" value={cfg.confidentiality}
|
|
onChange={e => onChange({ confidentiality: e.target.value as ReportConfig['confidentiality'] })}>
|
|
<option value="Confidential">Confidential</option>
|
|
<option value="Internal">Internal</option>
|
|
<option value="Public">Public</option>
|
|
</select>
|
|
</Field>
|
|
|
|
</div>
|
|
);
|
|
}
|