Actualizar flujo CDC Brief: campaña existente y revisión final
This commit is contained in:
+10
-7
@@ -20,17 +20,19 @@ Este portal funciona por pasos y no como un chat libre.
|
|||||||
|
|
||||||
1. **Nota de voz** _(obligatorio)_
|
1. **Nota de voz** _(obligatorio)_
|
||||||
2. **Imágenes de referencia** _(obligatorio)_
|
2. **Imágenes de referencia** _(obligatorio)_
|
||||||
3. **Documentos relacionados y archivos de apoyo** _(opcional)_
|
3. **Documentos relacionados** _(opcional)_
|
||||||
4. **Enlaces de referencia online** _(opcional)_
|
4. **Campaña o elementos gráficos existentes** _(opcional)_
|
||||||
|
5. **Enlaces de referencia online** _(opcional)_
|
||||||
|
6. **Revisión y envío**
|
||||||
|
|
||||||
**Qué hace el sistema con cada entrada:**
|
**Qué hace el sistema con cada entrada:**
|
||||||
|
|
||||||
- **Nota de voz:** se analiza
|
- **Nota de voz:** se analiza
|
||||||
- **Imágenes:** se analizan
|
- **Imágenes:** se analizan
|
||||||
- **PDF, Excel, PowerPoint, Word y otros archivos:** se guardan en la carpeta del proyecto
|
- **Documentos y Campaña:** se guardan en la carpeta del proyecto
|
||||||
- **Enlaces online:** se listan dentro del brief
|
- **Enlaces online:** se listan dentro del brief
|
||||||
|
|
||||||
Cuando completes los pasos obligatorios, podrás enviar la solicitud.`;
|
Cuando completes los pasos obligatorios, podrás enviar la solicitud en el paso final.`;
|
||||||
|
|
||||||
const [messages, setMessages] = useState<Message[]>([
|
const [messages, setMessages] = useState<Message[]>([
|
||||||
{
|
{
|
||||||
@@ -145,7 +147,8 @@ Cuando completes los pasos obligatorios, podrás enviar la solicitud.`;
|
|||||||
formData.append('timestamp', new Date().toISOString());
|
formData.append('timestamp', new Date().toISOString());
|
||||||
|
|
||||||
attachments.forEach((att, index) => {
|
attachments.forEach((att, index) => {
|
||||||
formData.append(`file_${index}`, att.file);
|
const key = att.category === 'campaign' ? `campaign_file_${index}` : `file_${index}`;
|
||||||
|
formData.append(key, att.file);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (audio) {
|
if (audio) {
|
||||||
@@ -288,8 +291,8 @@ Verifica que la URL productiva esté correcta en el entorno y que el endpoint es
|
|||||||
|
|
||||||
<div className="mt-4 space-y-4">
|
<div className="mt-4 space-y-4">
|
||||||
{[
|
{[
|
||||||
'La nota de voz y las imágenes sí se analizan automáticamente.',
|
'La nota de voz, las imágenes y los PDFs se analizan automáticamente.',
|
||||||
'PDF, Excel, PowerPoint, Word y otros adjuntos se guardan en la carpeta del proyecto.',
|
'Excel, PowerPoint, Word y otros adjuntos se guardan como soporte en la carpeta del proyecto.',
|
||||||
'Los enlaces online se registran para alimentar el brief final.',
|
'Los enlaces online se registran para alimentar el brief final.',
|
||||||
'El usuario no necesita usar el portal como chat libre: todo queda guiado.',
|
'El usuario no necesita usar el portal como chat libre: todo queda guiado.',
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
|
|||||||
+191
-15
@@ -23,7 +23,7 @@ interface InputAreaProps {
|
|||||||
isProcessing: boolean;
|
isProcessing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type GuidedStep = 'audio' | 'images' | 'documents' | 'links';
|
type GuidedStep = 'audio' | 'images' | 'documents' | 'campaign' | 'links' | 'review';
|
||||||
|
|
||||||
interface StepDefinition {
|
interface StepDefinition {
|
||||||
id: GuidedStep;
|
id: GuidedStep;
|
||||||
@@ -52,14 +52,28 @@ const STEP_DEFINITIONS: StepDefinition[] = [
|
|||||||
id: 'documents',
|
id: 'documents',
|
||||||
number: 3,
|
number: 3,
|
||||||
title: 'Documentos relacionados',
|
title: 'Documentos relacionados',
|
||||||
hint: 'Opcional. Adjunta PDF, Excel, PowerPoint, Word u otros archivos de apoyo.',
|
hint: 'Opcional. Adjunta archivos de apoyo.',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'campaign',
|
||||||
|
number: 4,
|
||||||
|
title: 'Campaña o elementos gráficos',
|
||||||
|
hint: 'Opcional. Si ya existen piezas gráficas o logos.',
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'links',
|
id: 'links',
|
||||||
number: 4,
|
number: 5,
|
||||||
title: 'Enlaces de referencia online',
|
title: 'Enlaces de referencia',
|
||||||
hint: 'Opcional. Pega enlaces, uno por línea.',
|
hint: 'Opcional. Pega enlaces online.',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'review',
|
||||||
|
number: 6,
|
||||||
|
title: 'Revisión y envío',
|
||||||
|
hint: 'Finaliza tu solicitud aquí.',
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -71,6 +85,7 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
const [recordingDuration, setRecordingDuration] = useState(0);
|
const [recordingDuration, setRecordingDuration] = useState(0);
|
||||||
const [audioRecording, setAudioRecording] = useState<AudioRecording | null>(null);
|
const [audioRecording, setAudioRecording] = useState<AudioRecording | null>(null);
|
||||||
const [currentStep, setCurrentStep] = useState<GuidedStep>('audio');
|
const [currentStep, setCurrentStep] = useState<GuidedStep>('audio');
|
||||||
|
const [hasCampaign, setHasCampaign] = useState<'yes' | 'no' | null>(null);
|
||||||
const [validationMessage, setValidationMessage] = useState<string | null>(null);
|
const [validationMessage, setValidationMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
@@ -84,7 +99,11 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
[attachments],
|
[attachments],
|
||||||
);
|
);
|
||||||
const documentAttachments = useMemo(
|
const documentAttachments = useMemo(
|
||||||
() => attachments.filter((att) => att.type !== 'image'),
|
() => attachments.filter((att) => att.type !== 'image' && att.category !== 'campaign'),
|
||||||
|
[attachments],
|
||||||
|
);
|
||||||
|
const campaignAttachments = useMemo(
|
||||||
|
() => attachments.filter((att) => att.category === 'campaign'),
|
||||||
[attachments],
|
[attachments],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -109,8 +128,14 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
return imageAttachments.length > 0 ? 'done' : currentStep === 'images' ? 'active' : 'pending';
|
return imageAttachments.length > 0 ? 'done' : currentStep === 'images' ? 'active' : 'pending';
|
||||||
case 'documents':
|
case 'documents':
|
||||||
return documentAttachments.length > 0 ? 'done' : currentStep === 'documents' ? 'active' : 'pending';
|
return documentAttachments.length > 0 ? 'done' : currentStep === 'documents' ? 'active' : 'pending';
|
||||||
|
case 'campaign':
|
||||||
|
if (hasCampaign === 'no') return 'done';
|
||||||
|
if (hasCampaign === 'yes' && campaignAttachments.length > 0) return 'done';
|
||||||
|
return currentStep === 'campaign' ? 'active' : 'pending';
|
||||||
case 'links':
|
case 'links':
|
||||||
return text.trim() ? 'done' : currentStep === 'links' ? 'active' : 'pending';
|
return text.trim() ? 'done' : currentStep === 'links' ? 'active' : 'pending';
|
||||||
|
case 'review':
|
||||||
|
return currentStep === 'review' ? 'active' : 'pending';
|
||||||
default:
|
default:
|
||||||
return 'pending';
|
return 'pending';
|
||||||
}
|
}
|
||||||
@@ -158,6 +183,7 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
file,
|
file,
|
||||||
type: inferAttachmentType(file),
|
type: inferAttachmentType(file),
|
||||||
url: URL.createObjectURL(file),
|
url: URL.createObjectURL(file),
|
||||||
|
category: currentStep === 'campaign' ? 'campaign' : 'general',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setAttachments((prev) => [...prev, ...newAttachments]);
|
setAttachments((prev) => [...prev, ...newAttachments]);
|
||||||
@@ -254,9 +280,27 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentStep === 'documents') {
|
if (currentStep === 'documents') {
|
||||||
|
advanceTo('campaign');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStep === 'campaign') {
|
||||||
|
if (hasCampaign === null) {
|
||||||
|
setValidationMessage('Por favor responde si ya existe campaña o elementos gráficos.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasCampaign === 'yes' && campaignAttachments.length === 0) {
|
||||||
|
setValidationMessage('Si seleccionaste que sí existe campaña, debes adjuntar al menos un archivo.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
advanceTo('links');
|
advanceTo('links');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentStep === 'links') {
|
||||||
|
advanceTo('review');
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildSubmissionText = () => {
|
const buildSubmissionText = () => {
|
||||||
@@ -268,11 +312,19 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
|
|
||||||
const summaryLines = [
|
const summaryLines = [
|
||||||
'Solicitud enviada desde el portal guiado del Brief CDC.',
|
'Solicitud enviada desde el portal guiado del Brief CDC.',
|
||||||
`Nota de voz: ${audioRecording ? 'sí' : 'no'}`,
|
`Nota de voz: ${audioRecording ? 'Sí' : 'No'}`,
|
||||||
`Imágenes de referencia: ${imageAttachments.length}`,
|
`Imágenes de referencia: ${imageAttachments.length}`,
|
||||||
`Documentos relacionados: ${documentAttachments.length}`,
|
`Documentos relacionados: ${documentAttachments.length}`,
|
||||||
|
`¿Existe campaña o elementos gráficos?: ${hasCampaign === 'yes' ? 'Sí' : 'No'}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (hasCampaign === 'yes') {
|
||||||
|
summaryLines.push(`Archivos de campaña adjuntos: ${campaignAttachments.length}`);
|
||||||
|
campaignAttachments.forEach((att) => {
|
||||||
|
summaryLines.push(`- ${att.file.name}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (linksText) {
|
if (linksText) {
|
||||||
summaryLines.push('', 'Enlaces de referencia:', linksText);
|
summaryLines.push('', 'Enlaces de referencia:', linksText);
|
||||||
}
|
}
|
||||||
@@ -281,8 +333,13 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
|
if (currentStep !== 'review') {
|
||||||
|
advanceTo('review');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!requiredCompleted) {
|
if (!requiredCompleted) {
|
||||||
setValidationMessage('Completa primero la nota de voz y al menos una imagen de referencia.');
|
setValidationMessage('Completa primero los pasos obligatorios (voz e imágenes).');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +347,7 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
onSendMessage(buildSubmissionText(), attachments, audioRecording || undefined);
|
onSendMessage(buildSubmissionText(), attachments, audioRecording || undefined);
|
||||||
setText('');
|
setText('');
|
||||||
setAttachments([]);
|
setAttachments([]);
|
||||||
|
setHasCampaign(null);
|
||||||
if (audioRecording?.url) {
|
if (audioRecording?.url) {
|
||||||
URL.revokeObjectURL(audioRecording.url);
|
URL.revokeObjectURL(audioRecording.url);
|
||||||
}
|
}
|
||||||
@@ -326,7 +384,7 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-3 xl:grid-cols-4">
|
<div className="grid gap-3 xl:grid-cols-6">
|
||||||
{STEP_DEFINITIONS.map((step) => {
|
{STEP_DEFINITIONS.map((step) => {
|
||||||
const status = getStepStatus(step.id);
|
const status = getStepStatus(step.id);
|
||||||
return (
|
return (
|
||||||
@@ -335,7 +393,7 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => advanceTo(step.id)}
|
onClick={() => advanceTo(step.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'group relative overflow-hidden rounded-[24px] border p-4 text-left transition-all',
|
'group relative flex h-full flex-col overflow-hidden rounded-[24px] border p-4 text-left transition-all',
|
||||||
status === 'done' && 'border-emerald-400/25 bg-emerald-500/10',
|
status === 'done' && 'border-emerald-400/25 bg-emerald-500/10',
|
||||||
status === 'active' && 'border-indigo-400/30 bg-[linear-gradient(180deg,rgba(99,102,241,0.18),rgba(59,130,246,0.08))] shadow-[0_18px_40px_rgba(79,70,229,0.18)]',
|
status === 'active' && 'border-indigo-400/30 bg-[linear-gradient(180deg,rgba(99,102,241,0.18),rgba(59,130,246,0.08))] shadow-[0_18px_40px_rgba(79,70,229,0.18)]',
|
||||||
status === 'pending' && 'border-white/10 bg-black/20 hover:border-white/20 hover:bg-black/25'
|
status === 'pending' && 'border-white/10 bg-black/20 hover:border-white/20 hover:bg-black/25'
|
||||||
@@ -353,8 +411,8 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
<div className="h-3 w-3 rounded-full bg-zinc-600" />
|
<div className="h-3 w-3 rounded-full bg-zinc-600" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h4 className="text-sm font-semibold text-white">{step.title}</h4>
|
<h4 className="mb-2 min-h-[2.5rem] text-sm font-semibold text-white">{step.title}</h4>
|
||||||
<p className="mt-2 text-xs leading-6 text-zinc-400">{step.hint}</p>
|
<p className="min-h-[3rem] text-xs leading-5 text-zinc-400">{step.hint}</p>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -489,6 +547,67 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{currentStep === 'campaign' && (
|
||||||
|
<div className="rounded-[24px] border border-white/10 bg-white/5 p-4">
|
||||||
|
<p className="mb-5 text-sm leading-7 text-zinc-300">
|
||||||
|
¿Ya existe campaña o elementos gráficos para esta solicitud? (logos, key visuals, piezas previas, etc.)
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-4 mb-6">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setHasCampaign('yes');
|
||||||
|
setValidationMessage(null);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
"px-6 py-3 rounded-2xl font-semibold transition-all border",
|
||||||
|
hasCampaign === 'yes'
|
||||||
|
? "bg-indigo-500/20 border-indigo-400 text-white shadow-[0_0_20px_rgba(99,102,241,0.2)]"
|
||||||
|
: "bg-black/20 border-white/10 text-zinc-400 hover:border-white/20"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Sí, ya existen
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setHasCampaign('no');
|
||||||
|
// Remove any campaign attachments if user switches to 'no'
|
||||||
|
setAttachments(prev => prev.filter(a => a.category !== 'campaign'));
|
||||||
|
setValidationMessage(null);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
"px-6 py-3 rounded-2xl font-semibold transition-all border",
|
||||||
|
hasCampaign === 'no'
|
||||||
|
? "bg-indigo-500/20 border-indigo-400 text-white shadow-[0_0_20px_rgba(99,102,241,0.2)]"
|
||||||
|
: "bg-black/20 border-white/10 text-zinc-400 hover:border-white/20"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
No, empezar de cero
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasCampaign === 'yes' && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="space-y-4"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3 text-sm text-indigo-300 mb-2">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<span>Debes adjuntar al menos un archivo de la campaña.</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => fileInputRef.current?.click()}
|
||||||
|
className="inline-flex items-center gap-2 rounded-2xl bg-[linear-gradient(135deg,#6366f1_0%,#2563eb_100%)] px-5 py-3.5 text-sm font-semibold text-white shadow-[0_18px_40px_rgba(79,70,229,0.25)] transition hover:brightness-110 disabled:opacity-50"
|
||||||
|
disabled={isProcessing || isRecording}
|
||||||
|
>
|
||||||
|
<Paperclip className="h-4 w-4" />
|
||||||
|
Adjuntar elementos de campaña
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{currentStep === 'links' && (
|
{currentStep === 'links' && (
|
||||||
<div className="rounded-[24px] border border-white/10 bg-white/5 p-4">
|
<div className="rounded-[24px] border border-white/10 bg-white/5 p-4">
|
||||||
<p className="mb-5 text-sm leading-7 text-zinc-300">
|
<p className="mb-5 text-sm leading-7 text-zinc-300">
|
||||||
@@ -510,6 +629,57 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{currentStep === 'review' && (
|
||||||
|
<div className="rounded-[24px] border border-white/10 bg-white/5 p-4">
|
||||||
|
<p className="mb-5 text-sm leading-7 text-zinc-300">
|
||||||
|
Revisa los detalles de tu solicitud antes de enviarla.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
<div className="rounded-2xl border border-white/10 bg-black/20 p-4">
|
||||||
|
<h4 className="text-xs font-semibold uppercase tracking-widest text-zinc-500 mb-3">Contenido Principal</h4>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-zinc-400">Nota de Voz:</span>
|
||||||
|
<span className={cn("font-medium", audioRecording ? "text-emerald-400" : "text-amber-400")}>
|
||||||
|
{audioRecording ? "Cargada" : "Faltante"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-zinc-400">Imágenes:</span>
|
||||||
|
<span className={cn("font-medium", imageAttachments.length > 0 ? "text-emerald-400" : "text-amber-400")}>
|
||||||
|
{imageAttachments.length} archivo(s)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-2xl border border-white/10 bg-black/20 p-4">
|
||||||
|
<h4 className="text-xs font-semibold uppercase tracking-widest text-zinc-500 mb-3">Archivos Adicionales</h4>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-zinc-400">Documentos:</span>
|
||||||
|
<span className="text-zinc-200 font-medium">{documentAttachments.length} archivo(s)</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-zinc-400">Campaña Previa:</span>
|
||||||
|
<span className="text-zinc-200 font-medium">
|
||||||
|
{hasCampaign === 'yes' ? `${campaignAttachments.length} archivo(s)` : 'No aplica'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 rounded-2xl border border-white/10 bg-black/20 p-4">
|
||||||
|
<h4 className="text-xs font-semibold uppercase tracking-widest text-zinc-500 mb-2">Enlaces y Referencias</h4>
|
||||||
|
<p className="text-sm text-zinc-300 italic whitespace-pre-wrap">
|
||||||
|
{text.trim() ? text : "Sin enlaces agregados"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
multiple
|
multiple
|
||||||
@@ -534,7 +704,7 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{currentStep !== 'links' ? (
|
{currentStep !== 'review' ? (
|
||||||
<button
|
<button
|
||||||
onClick={handleContinue}
|
onClick={handleContinue}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
@@ -543,8 +713,7 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
Continuar
|
Continuar
|
||||||
<ChevronRight className="h-4 w-4" />
|
<ChevronRight className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : (
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={isProcessing || !requiredCompleted}
|
disabled={isProcessing || !requiredCompleted}
|
||||||
@@ -553,6 +722,7 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
{isProcessing ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
|
{isProcessing ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
|
||||||
Finalizar y enviar
|
Finalizar y enviar
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -584,6 +754,12 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
|
|||||||
<span>Documentos</span>
|
<span>Documentos</span>
|
||||||
<span className="font-semibold text-white">{documentAttachments.length}</span>
|
<span className="font-semibold text-white">{documentAttachments.length}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span>Campaña</span>
|
||||||
|
<span className={cn('font-semibold', hasCampaign === 'yes' ? 'text-emerald-300' : 'text-zinc-500')}>
|
||||||
|
{hasCampaign === 'yes' ? `${campaignAttachments.length} archivo(s)` : hasCampaign === 'no' ? 'No aplica' : 'Pendiente'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span>Enlaces</span>
|
<span>Enlaces</span>
|
||||||
<span className="font-semibold text-white">{text.split('\n').map(line => line.trim()).filter(Boolean).length}</span>
|
<span className="font-semibold text-white">{text.split('\n').map(line => line.trim()).filter(Boolean).length}</span>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export interface Attachment {
|
|||||||
file: File;
|
file: File;
|
||||||
type: AttachmentType;
|
type: AttachmentType;
|
||||||
url: string; // Object URL for preview
|
url: string; // Object URL for preview
|
||||||
|
category?: 'general' | 'campaign';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AudioRecording {
|
export interface AudioRecording {
|
||||||
|
|||||||
Reference in New Issue
Block a user