Files
bakery-ia/frontend/src/components/domain/analytics/ExportOptions.tsx.backup
2025-08-28 10:41:04 +02:00

592 lines
19 KiB
Plaintext

import React, { useState } from 'react';
import { Card, Button, Badge, Input, Select } from '../../ui';
import type {
ExportOptionsProps,
ExportOptions,
ExportFormat,
ExportTemplate,
ReportSchedule
} from './types';
const FORMAT_LABELS: Record<ExportFormat, string> = {
pdf: 'PDF',
excel: 'Excel',
csv: 'CSV',
png: 'PNG',
svg: 'SVG',
json: 'JSON',
};
const FORMAT_DESCRIPTIONS: Record<ExportFormat, string> = {
pdf: 'Documento PDF con formato profesional',
excel: 'Hoja de cálculo de Microsoft Excel',
csv: 'Archivo de valores separados por comas',
png: 'Imagen PNG de alta calidad',
svg: 'Imagen vectorial escalable',
json: 'Datos estructurados en formato JSON',
};
const FORMAT_ICONS: Record<ExportFormat, string> = {
pdf: '📄',
excel: '📊',
csv: '📋',
png: '🖼️',
svg: '🎨',
json: '⚙️',
};
const FREQUENCY_OPTIONS = [
{ value: 'daily', label: 'Diario' },
{ value: 'weekly', label: 'Semanal' },
{ value: 'monthly', label: 'Mensual' },
{ value: 'quarterly', label: 'Trimestral' },
{ value: 'yearly', label: 'Anual' },
];
const TIME_OPTIONS = [
{ value: '06:00', label: '6:00 AM' },
{ value: '09:00', label: '9:00 AM' },
{ value: '12:00', label: '12:00 PM' },
{ value: '15:00', label: '3:00 PM' },
{ value: '18:00', label: '6:00 PM' },
{ value: '21:00', label: '9:00 PM' },
];
const DAYS_OF_WEEK = [
{ value: 1, label: 'Lunes' },
{ value: 2, label: 'Martes' },
{ value: 3, label: 'Miércoles' },
{ value: 4, label: 'Jueves' },
{ value: 5, label: 'Viernes' },
{ value: 6, label: 'Sábado' },
{ value: 0, label: 'Domingo' },
];
interface ExportOptionsComponentProps extends ExportOptionsProps {
onClose?: () => void;
}
export const ExportOptions: React.FC<ExportOptionsComponentProps> = ({
type,
title,
description,
availableFormats = ['pdf', 'excel', 'csv', 'png'],
templates = [],
onExport,
onSchedule,
loading = false,
disabled = false,
showScheduling = true,
showTemplates = true,
showAdvanced = true,
defaultOptions,
className = '',
onClose,
}) => {
const [selectedFormat, setSelectedFormat] = useState<ExportFormat>(
defaultOptions?.format || availableFormats[0]
);
const [selectedTemplate, setSelectedTemplate] = useState<string>(
templates.find(t => t.is_default)?.id || ''
);
const [exportOptions, setExportOptions] = useState<Partial<ExportOptions>>({
include_headers: true,
include_filters: true,
include_summary: true,
date_format: 'DD/MM/YYYY',
number_format: '#,##0.00',
currency_format: '€#,##0.00',
locale: 'es-ES',
timezone: 'Europe/Madrid',
page_size: 'A4',
orientation: 'portrait',
password_protected: false,
...defaultOptions,
});
const [scheduleOptions, setScheduleOptions] = useState<Partial<ReportSchedule>>({
enabled: false,
frequency: 'weekly',
time: '09:00',
days_of_week: [1], // Monday
recipients: [],
format: selectedFormat,
include_attachments: true,
});
const [recipientsInput, setRecipientsInput] = useState('');
const [activeTab, setActiveTab] = useState<'export' | 'schedule'>('export');
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
const handleExportOptionChange = (key: keyof ExportOptions, value: any) => {
setExportOptions(prev => ({ ...prev, [key]: value }));
};
const handleScheduleOptionChange = (key: keyof ReportSchedule, value: any) => {
setScheduleOptions(prev => ({ ...prev, [key]: value }));
};
const handleExport = () => {
if (onExport) {
const fullOptions: ExportOptions = {
format: selectedFormat,
template: selectedTemplate || undefined,
include_headers: true,
include_filters: true,
include_summary: true,
date_format: 'DD/MM/YYYY',
number_format: '#,##0.00',
currency_format: '€#,##0.00',
locale: 'es-ES',
timezone: 'Europe/Madrid',
page_size: 'A4',
orientation: 'portrait',
...exportOptions,
// format: selectedFormat, // Ensure format matches selection - handled by exportOptions
};
onExport(fullOptions);
}
};
const handleSchedule = () => {
if (onSchedule) {
const recipients = recipientsInput
.split(',')
.map(email => email.trim())
.filter(email => email.length > 0);
const fullSchedule: ReportSchedule = {
enabled: true,
frequency: 'weekly',
time: '09:00',
recipients: recipients,
format: selectedFormat,
include_attachments: true,
...scheduleOptions,
};
onSchedule(fullSchedule);
}
};
const renderFormatSelector = () => (
<div className="space-y-3">
<h4 className="font-medium text-gray-900">Formato de exportación</h4>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{availableFormats.map(format => (
<button
key={format}
onClick={() => setSelectedFormat(format)}
className={`p-3 border rounded-lg text-left transition-colors ${
selectedFormat === format
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-gray-300'
}`}
disabled={disabled || loading}
>
<div className="flex items-center gap-2 mb-1">
<span className="text-xl">{FORMAT_ICONS[format]}</span>
<span className="font-medium">{FORMAT_LABELS[format]}</span>
</div>
<p className="text-sm text-gray-600">{FORMAT_DESCRIPTIONS[format]}</p>
</button>
))}
</div>
</div>
);
const renderTemplateSelector = () => {
if (!showTemplates || templates.length === 0) return null;
return (
<div className="space-y-3">
<h4 className="font-medium text-gray-900">Plantilla</h4>
<Select
value={selectedTemplate}
onChange={(e) => setSelectedTemplate(e.target.value)}
disabled={disabled || loading}
>
<option value="">Plantilla predeterminada</option>
{templates.map(template => (
<option key={template.id} value={template.id}>
{template.name}
{template.is_default && ' (Predeterminada)'}
</option>
))}
</Select>
{selectedTemplate && (
<div className="text-sm text-gray-600">
{templates.find(t => t.id === selectedTemplate)?.description}
</div>
)}
</div>
);
};
const renderBasicOptions = () => (
<div className="space-y-4">
<h4 className="font-medium text-gray-900">Opciones básicas</h4>
<div className="space-y-3">
<label className="flex items-center">
<input
type="checkbox"
checked={exportOptions.include_headers || false}
onChange={(e) => handleExportOptionChange('include_headers', e.target.checked)}
className="mr-2 rounded"
disabled={disabled || loading}
/>
Incluir encabezados de columna
</label>
<label className="flex items-center">
<input
type="checkbox"
checked={exportOptions.include_filters || false}
onChange={(e) => handleExportOptionChange('include_filters', e.target.checked)}
className="mr-2 rounded"
disabled={disabled || loading}
/>
Incluir información de filtros aplicados
</label>
<label className="flex items-center">
<input
type="checkbox"
checked={exportOptions.include_summary || false}
onChange={(e) => handleExportOptionChange('include_summary', e.target.checked)}
className="mr-2 rounded"
disabled={disabled || loading}
/>
Incluir resumen y estadísticas
</label>
</div>
</div>
);
const renderAdvancedOptions = () => {
if (!showAdvanced || !showAdvancedOptions) return null;
return (
<div className="space-y-4 pt-4 border-t border-gray-200">
<h4 className="font-medium text-gray-900">Opciones avanzadas</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Formato de fecha
</label>
<Select
value={exportOptions.date_format || 'DD/MM/YYYY'}
onChange={(e) => handleExportOptionChange('date_format', e.target.value)}
disabled={disabled || loading}
>
<option value="DD/MM/YYYY">DD/MM/YYYY</option>
<option value="MM/DD/YYYY">MM/DD/YYYY</option>
<option value="YYYY-MM-DD">YYYY-MM-DD</option>
<option value="DD-MMM-YYYY">DD-MMM-YYYY</option>
</Select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Formato de números
</label>
<Select
value={exportOptions.number_format || '#,##0.00'}
onChange={(e) => handleExportOptionChange('number_format', e.target.value)}
disabled={disabled || loading}
>
<option value="#,##0.00">#,##0.00</option>
<option value="#.##0,00">#.##0,00</option>
<option value="#,##0">#,##0</option>
<option value="0.00">0.00</option>
</Select>
</div>
{(['pdf'] as ExportFormat[]).includes(selectedFormat) && (
<>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Tamaño de página
</label>
<Select
value={exportOptions.page_size || 'A4'}
onChange={(e) => handleExportOptionChange('page_size', e.target.value)}
disabled={disabled || loading}
>
<option value="A4">A4</option>
<option value="A3">A3</option>
<option value="Letter">Carta</option>
<option value="Legal">Legal</option>
</Select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Orientación
</label>
<Select
value={exportOptions.orientation || 'portrait'}
onChange={(e) => handleExportOptionChange('orientation', e.target.value)}
disabled={disabled || loading}
>
<option value="portrait">Vertical</option>
<option value="landscape">Horizontal</option>
</Select>
</div>
</>
)}
</div>
<div>
<label className="flex items-center">
<input
type="checkbox"
checked={exportOptions.password_protected || false}
onChange={(e) => handleExportOptionChange('password_protected', e.target.checked)}
className="mr-2 rounded"
disabled={disabled || loading}
/>
Proteger con contraseña
</label>
{exportOptions.password_protected && (
<div className="mt-2">
<Input
type="password"
placeholder="Contraseña"
value={exportOptions.password || ''}
onChange={(e) => handleExportOptionChange('password', e.target.value)}
disabled={disabled || loading}
/>
</div>
)}
</div>
</div>
);
};
const renderScheduleTab = () => (
<div className="space-y-4">
<div>
<label className="flex items-center mb-4">
<input
type="checkbox"
checked={scheduleOptions.enabled || false}
onChange={(e) => handleScheduleOptionChange('enabled', e.target.checked)}
className="mr-2 rounded"
disabled={disabled || loading}
/>
<span className="font-medium text-gray-900">Habilitar programación automática</span>
</label>
</div>
{scheduleOptions.enabled && (
<div className="space-y-4 pl-6 border-l-2 border-blue-200">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Frecuencia
</label>
<Select
value={scheduleOptions.frequency || 'weekly'}
onChange={(e) => handleScheduleOptionChange('frequency', e.target.value)}
disabled={disabled || loading}
>
{FREQUENCY_OPTIONS.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</Select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Hora de envío
</label>
<Select
value={scheduleOptions.time || '09:00'}
onChange={(e) => handleScheduleOptionChange('time', e.target.value)}
disabled={disabled || loading}
>
{TIME_OPTIONS.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</Select>
</div>
</div>
{scheduleOptions.frequency === 'weekly' && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Días de la semana
</label>
<div className="flex flex-wrap gap-2">
{DAYS_OF_WEEK.map(day => (
<label key={day.value} className="flex items-center">
<input
type="checkbox"
checked={(scheduleOptions.days_of_week || []).includes(day.value)}
onChange={(e) => {
const currentDays = scheduleOptions.days_of_week || [];
const newDays = e.target.checked
? [...currentDays, day.value]
: currentDays.filter(d => d !== day.value);
handleScheduleOptionChange('days_of_week', newDays);
}}
className="mr-1 rounded"
disabled={disabled || loading}
/>
<span className="text-sm">{day.label}</span>
</label>
))}
</div>
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Destinatarios (emails separados por comas)
</label>
<Input
type="text"
value={recipientsInput}
onChange={(e) => setRecipientsInput(e.target.value)}
placeholder="ejemplo@empresa.com, otro@empresa.com"
disabled={disabled || loading}
/>
</div>
<div>
<label className="flex items-center">
<input
type="checkbox"
checked={scheduleOptions.include_attachments || false}
onChange={(e) => handleScheduleOptionChange('include_attachments', e.target.checked)}
className="mr-2 rounded"
disabled={disabled || loading}
/>
Incluir archivo adjunto con los datos
</label>
</div>
</div>
)}
</div>
);
return (
<div className={`bg-white rounded-lg shadow-lg max-w-2xl mx-auto ${className}`}>
<Card className="p-0">
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-gray-200">
<div>
<h2 className="text-xl font-semibold text-gray-900">{title}</h2>
{description && (
<p className="text-sm text-gray-600 mt-1">{description}</p>
)}
</div>
{onClose && (
<Button variant="ghost" size="sm" onClick={onClose}>
</Button>
)}
</div>
{/* Tabs */}
<div className="border-b border-gray-200">
<nav className="flex space-x-8 px-6">
<button
onClick={() => setActiveTab('export')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'export'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
📥 Exportación inmediata
</button>
{showScheduling && (
<button
onClick={() => setActiveTab('schedule')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'schedule'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
📅 Programación
</button>
)}
</nav>
</div>
{/* Content */}
<div className="p-6 space-y-6" style={{ maxHeight: '70vh', overflowY: 'auto' }}>
{activeTab === 'export' ? (
<>
{renderFormatSelector()}
{renderTemplateSelector()}
{renderBasicOptions()}
{showAdvanced && (
<div>
<Button
variant="ghost"
size="sm"
onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}
className="text-blue-600"
>
{showAdvancedOptions ? '▲' : '▼'} Opciones avanzadas
</Button>
</div>
)}
{renderAdvancedOptions()}
</>
) : (
renderScheduleTab()
)}
</div>
{/* Footer */}
<div className="flex justify-end gap-3 p-6 border-t border-gray-200">
{onClose && (
<Button
variant="outline"
onClick={onClose}
disabled={loading}
>
Cancelar
</Button>
)}
{activeTab === 'export' ? (
<Button
variant="primary"
onClick={handleExport}
disabled={disabled || loading}
>
{loading ? '🔄 Exportando...' : `${FORMAT_ICONS[selectedFormat]} Exportar ${FORMAT_LABELS[selectedFormat]}`}
</Button>
) : (
<Button
variant="primary"
onClick={handleSchedule}
disabled={disabled || loading || !scheduleOptions.enabled || !recipientsInput.trim()}
>
{loading ? '🔄 Programando...' : '📅 Programar reporte'}
</Button>
)}
</div>
</Card>
</div>
);
};
export default ExportOptions;