102 lines
4.4 KiB
TypeScript
102 lines
4.4 KiB
TypeScript
import type { FC } from 'react';
|
|
import { useEffect, useState } from 'react';
|
|
import { Save, Megaphone } from 'lucide-react';
|
|
import { Button, Input, toast } from './ui';
|
|
import {
|
|
odooApi, CAMPANAS_DEFAULT, type ClinicSettings, type CampanasSettings,
|
|
} from '../services/odoo';
|
|
|
|
interface Props {
|
|
settings: ClinicSettings;
|
|
onSaved: (settings: ClinicSettings) => void;
|
|
}
|
|
|
|
const Toggle: FC<{ label: string; checked: boolean; onChange: (v: boolean) => void; ayuda?: string }> = ({ label, checked, onChange, ayuda }) => (
|
|
<label className="flex items-start gap-3 cursor-pointer">
|
|
<button
|
|
type="button"
|
|
role="switch"
|
|
aria-checked={checked}
|
|
onClick={() => onChange(!checked)}
|
|
className={`mt-0.5 relative h-6 w-11 shrink-0 rounded-full transition-colors ${checked ? 'bg-theme-accent' : 'bg-theme-border-strong'}`}
|
|
>
|
|
<span className={`absolute top-0.5 h-5 w-5 rounded-full bg-white transition-transform ${checked ? 'translate-x-5' : 'translate-x-0.5'}`} />
|
|
</button>
|
|
<span>
|
|
<span className="block text-sm font-medium text-theme-heading">{label}</span>
|
|
{ayuda && <span className="block text-xs text-theme-muted">{ayuda}</span>}
|
|
</span>
|
|
</label>
|
|
);
|
|
|
|
// Configuración → Campañas: bloque `campanas`. Apagado (default) no aparece la
|
|
// sección, no corre el cron y no se envía nada.
|
|
const CampanasPanel: FC<Props> = ({ settings, onSaved }) => {
|
|
const [form, setForm] = useState<CampanasSettings>(settings.campanas || CAMPANAS_DEFAULT);
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
useEffect(() => setForm(settings.campanas || CAMPANAS_DEFAULT), [settings.campanas]);
|
|
|
|
const set = <K extends keyof CampanasSettings>(k: K, v: CampanasSettings[K]) =>
|
|
setForm((prev) => ({ ...prev, [k]: v }));
|
|
|
|
const guardar = async () => {
|
|
setSaving(true);
|
|
try {
|
|
const res = await odooApi.putClinicSettings({
|
|
campanas: { ...form }, motivo: 'Configuración de campañas',
|
|
});
|
|
if (res.status === 'success') {
|
|
onSaved(res.settings);
|
|
toast.success('Configuración de campañas guardada');
|
|
}
|
|
} catch (err) {
|
|
const msg = (err as { response?: { data?: { message?: string } } })?.response?.data?.message;
|
|
toast.error(msg || 'No se pudo guardar la configuración de campañas');
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div>
|
|
<h3 className="font-heading text-lg text-theme-heading flex items-center gap-2">
|
|
<Megaphone size={18} /> Campañas por segmentos
|
|
</h3>
|
|
<p className="text-sm text-theme-muted mt-1">
|
|
Apagado (default) no existe la sección Campañas, no corre ningún proceso y no se envía
|
|
nada. Encendido, aparecen las pantallas de Campañas y Segmentos.
|
|
</p>
|
|
</div>
|
|
<Button onClick={guardar} loading={saving}><Save size={16} className="mr-1.5" />Guardar</Button>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<Toggle label="Activar campañas" checked={form.activo} onChange={(v) => set('activo', v)}
|
|
ayuda="Muestra la sección Campañas y habilita el proceso automático cada 5 minutos." />
|
|
<Toggle label="Exigir consentimiento de marketing" checked={form.exigir_consentimiento}
|
|
onChange={(v) => set('exigir_consentimiento', v)}
|
|
ayuda="Un paciente sin consentimiento nunca recibe una campaña (los avisos de cita sí siguen)." />
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
<Input type="number" min={1} max={600} label="Envíos por minuto"
|
|
value={form.envios_por_minuto}
|
|
onChange={(e) => set('envios_por_minuto', Number(e.target.value) || 1)} />
|
|
<Input type="number" min={1} max={100} label="Errores seguidos para pausar"
|
|
value={form.pausar_tras_errores}
|
|
onChange={(e) => set('pausar_tras_errores', Number(e.target.value) || 1)} />
|
|
<Input type="number" min={1} max={90} label="Ventana de atribución (días)"
|
|
value={form.ventana_atribucion_dias}
|
|
onChange={(e) => set('ventana_atribucion_dias', Number(e.target.value) || 1)} />
|
|
<Input type="time" label="Hora de envío (recurrentes)" value={form.hora_envio}
|
|
onChange={(e) => set('hora_envio', e.target.value)} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CampanasPanel;
|