225 lines
9.0 KiB
TypeScript
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>
|
|
);
|
|
}
|