Files
Investidor-V2/resources/js/components/opportunity-contact-form.tsx
T
2026-11-02 20:31:15 +02:00

225 lines
9.0 KiB
TypeScript

import { useState } from 'react';
import { button, control } from '@/pages/management/shared';
export default function OpportunityContactForm({
id,
etag,
organization,
investors,
disabled,
onSaved,
}: {
id: number;
etag: string;
organization: string | null;
investors: { id: string; legal_name: string }[];
disabled: boolean;
onSaved: (contact: { id: string; full_name: string }) => void;
}) {
const [values, setValues] = useState({
full_name: '',
email: '',
phone: '',
investor_id: '',
investor_type: '',
});
const [busy, setBusy] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
const [message, setMessage] = useState('');
async function save() {
if (busy || disabled) return;
setBusy(true);
setErrors({});
setMessage('');
try {
const xsrf = document.cookie
.split('; ')
.find((item) => item.startsWith('XSRF-TOKEN='));
const response = await fetch(
`/minhas-oportunidades/${id}/contacts`,
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-XSRF-TOKEN': xsrf
? decodeURIComponent(xsrf.slice(11))
: '',
},
body: JSON.stringify({
...values,
investor_id: values.investor_id || null,
investor_type: values.investor_type || null,
etag,
}),
},
);
const result = await response.json();
setMessage(
result.message ??
'Não foi possível guardar o contacto. Os valores foram preservados.',
);
if (!response.ok) {
setErrors(
Object.fromEntries(
Object.entries(result.errors ?? {}).map(
([key, value]) => [
key,
Array.isArray(value)
? value.join(' ')
: String(value),
],
),
),
);
return;
}
onSaved(result.contact);
} catch {
setMessage(
'Resposta indisponível. Recupere a oportunidade antes de repetir; os valores foram preservados.',
);
} finally {
setBusy(false);
requestAnimationFrame(() =>
document.getElementById('contact-result')?.focus(),
);
}
}
return (
<details className="rounded border p-3">
<summary>Cadastrar contacto do promotor</summary>
<p>
Indique os dados de contacto autorizados pelo promotor desta
oportunidade. O contacto fica guardado para reutilização e não é
publicado.
</p>
<fieldset disabled={disabled || busy} className="space-y-3">
<legend>Dados do contacto</legend>
{(['full_name', 'email', 'phone'] as const).map((field) => (
<div key={field}>
<label htmlFor={`contact-${field}`}>
{
{
full_name: 'Nome completo',
email: 'Email',
phone: 'Telefone internacional (exemplo: +258…)',
}[field]
}
</label>
<input
id={`contact-${field}`}
className={control}
type={
field === 'email'
? 'email'
: field === 'phone'
? 'tel'
: 'text'
}
value={values[field]}
onChange={(event) =>
setValues({
...values,
[field]: event.target.value,
})
}
aria-invalid={Boolean(errors[field])}
aria-describedby={
errors[field]
? `contact-${field}-error`
: undefined
}
/>
{errors[field] && (
<p id={`contact-${field}-error`} role="alert">
{errors[field]}
</p>
)}
</div>
))}
{organization && investors.length > 1 && (
<div>
<label htmlFor="contact-investor_id">
Promotor associado ao contacto
</label>
<select
id="contact-investor_id"
className={control}
value={values.investor_id}
onChange={(event) =>
setValues({
...values,
investor_id: event.target.value,
})
}
aria-invalid={Boolean(errors.investor_id)}
aria-describedby={
errors.investor_id
? 'contact-investor_id-error'
: undefined
}
>
<option value="">Por selecionar</option>
{investors.map((investor) => (
<option key={investor.id} value={investor.id}>
{investor.legal_name}
</option>
))}
</select>
{errors.investor_id && (
<p id="contact-investor_id-error" role="alert">
{errors.investor_id}
</p>
)}
</div>
)}
{organization && investors.length === 0 && (
<div>
<label htmlFor="contact-investor_type">
Classificação no cadastro do promotor
</label>
<select
id="contact-investor_type"
className={control}
value={values.investor_type}
onChange={(event) =>
setValues({
...values,
investor_type: event.target.value,
})
}
aria-invalid={Boolean(errors.investor_type)}
aria-describedby="contact-investor-type-help"
>
<option value="">Por indicar</option>
<option value="domestic">Nacional</option>
<option value="foreign">Estrangeiro</option>
<option value="institutional">Institucional</option>
<option value="individual">Individual</option>
</select>
<p id="contact-investor-type-help">
A organização já cadastrada será ligada ao cadastro
CRM existente, sem criar outra organização ou um
pedido fictício.
</p>
{errors.investor_type && (
<p role="alert">{errors.investor_type}</p>
)}
</div>
)}
<button
className={button}
type="button"
onClick={() => void save()}
>
Guardar contacto e selecionar
</button>
</fieldset>
<p id="contact-result" role="status" tabIndex={-1}>
{message}
</p>
</details>
);
}