Files
Investidor-V2/resources/js/pages/management/interest.tsx
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

370 lines
14 KiB
TypeScript

import { Link, useForm } from '@inertiajs/react';
import { Management, Field, button, control, states } from './shared';
type Interest = {
id: number;
reference: string;
full_name: string;
email: string;
phone: string | null;
investor_type: string;
country: string;
sector_interest: string;
opportunity_reference: string | null;
investment_range: string | null;
message: string;
status: string;
existing_business_id: string | null;
modality: string | null;
assigned_to: number | null;
next_action: string | null;
next_action_due_at: string | null;
consented_at: string | null;
reviewed_at: string | null;
reviewed_by: number | null;
created_at: string;
source_page: string | null;
metadata?: {
submission_id?: string;
journey_id?: string;
dossier_id?: string;
role?: string;
horizon?: string;
};
};
function dateTime(value: string | null) {
if (!value) return '—';
return new Intl.DateTimeFormat('pt-MZ', {
dateStyle: 'medium',
timeStyle: 'short',
timeZone: 'Africa/Maputo',
}).format(new Date(value));
}
type Transition = {
from_status: string | null;
to_status: string;
actor_id: number | null;
note: string | null;
created_at: string;
};
type Assignee = { id: number; name: string };
type Consent = {
purpose: string;
policy_version: string | null;
status: string;
granted_at: string | null;
withdrawn_at: string | null;
};
export default function InterestDetail({
interest,
history = [],
assignees = [],
consents = [],
declaredOrigin,
}: {
interest: Interest;
history?: Transition[];
assignees?: Assignee[];
consents?: Consent[];
declaredOrigin: { code: string | null; name: string };
}) {
const form = useForm({
status: interest.status,
note: '',
next_action: interest.next_action ?? '',
next_action_due_at: interest.next_action_due_at?.slice(0, 10) ?? '',
assigned_to: interest.assigned_to?.toString() ?? '',
});
const contactConsentForm = useForm({ reason: '' });
const sharingConsentForm = useForm({ reason: '' });
const rows: [string, string | number | null][] = [
['Nome', interest.full_name],
['Email', interest.email],
['Telefone', interest.phone],
['Tipo de investidor', interest.investor_type],
['País', interest.country],
['Página de entrada registada', interest.source_page],
[
'Origem declarada da solicitação',
declaredOrigin.code
? `${declaredOrigin.name} (${declaredOrigin.code})`
: declaredOrigin.name,
],
['Sector', interest.sector_interest],
['Oportunidade', interest.opportunity_reference],
['Negócio publicado', interest.existing_business_id],
['Modalidade', interest.modality],
['Faixa de investimento', interest.investment_range],
['Recebido em (Maputo)', dateTime(interest.created_at)],
['Consentimento em (Maputo)', dateTime(interest.consented_at)],
['Revisto em (Maputo)', dateTime(interest.reviewed_at)],
['Revisto pelo utilizador', interest.reviewed_by],
];
return (
<Management title={interest.reference}>
{interest.metadata?.submission_id && (
<Link
className="underline"
href={`/investidor/manifestacoes/${interest.metadata.submission_id}`}
>
Consultar manifestação completa, necessidades e anexos
privados
</Link>
)}
{interest.metadata?.dossier_id && (
<section className="space-y-2 rounded border p-4">
<h2 className="font-semibold">Contexto de investimento</h2>
<Link
className="underline"
href={`/management/portfolio/${interest.metadata.dossier_id}`}
>
Consultar corredor, cluster ou projecto de origem
</Link>
<p>Jornada: {interest.metadata.journey_id}</p>
<p>
Papel: {interest.metadata.role} · Horizonte:{' '}
{interest.metadata.horizon}
</p>
</section>
)}
<Link
className={button}
href={`/management/crm/convert/${interest.id}`}
>
Encaminhar para o CRM
</Link>
<dl className="grid gap-4 sm:grid-cols-2">
{rows.map(([label, value]) => (
<div key={label}>
<dt className="text-muted-foreground text-sm">
{label}
</dt>
<dd className="break-words">{value ?? '—'}</dd>
</div>
))}
</dl>
<section>
<h2 className="font-semibold">Mensagem</h2>
<p className="mt-2 break-words whitespace-pre-wrap">
{interest.message}
</p>
</section>
<section className="space-y-3">
<h2 className="font-semibold">Consentimentos e versões</h2>
{consents.map((consent, index) => (
<article
key={`${consent.purpose}-${index}`}
className="rounded border p-3"
>
<p>
{consent.purpose}: {consent.status}
</p>
<p className="text-sm">
Política {consent.policy_version ?? '—'} · concedido{' '}
{dateTime(consent.granted_at)}
</p>
{consent.withdrawn_at && (
<p className="text-sm">
Revogado {dateTime(consent.withdrawn_at)}
</p>
)}
</article>
))}
{consents.some(
(consent) =>
consent.purpose === 'respond_to_interest' &&
consent.status === 'granted',
) && (
<form
className="max-w-sm space-y-3"
onSubmit={(event) => {
event.preventDefault();
contactConsentForm.patch(
`/management/interests/${interest.id}/consent/contact`,
);
}}
>
<Field
label="Motivo do pedido de revogação"
error={contactConsentForm.errors.reason}
>
<textarea
className={control}
minLength={10}
maxLength={2000}
required
rows={3}
value={contactConsentForm.data.reason}
onChange={(event) =>
contactConsentForm.setData(
'reason',
event.target.value,
)
}
/>
</Field>
<button
className={button}
disabled={contactConsentForm.processing}
>
Revogar autorização de contacto
</button>
</form>
)}
{consents.some(
(consent) =>
consent.purpose === 'share_contact_with_proponent' &&
consent.status === 'granted',
) && (
<form
className="max-w-sm space-y-3"
onSubmit={(event) => {
event.preventDefault();
sharingConsentForm.patch(
`/management/interests/${interest.id}/consent/proponent`,
);
}}
>
<Field
label="Motivo do pedido de revogação"
error={sharingConsentForm.errors.reason}
>
<textarea
className={control}
minLength={10}
maxLength={2000}
required
rows={3}
value={sharingConsentForm.data.reason}
onChange={(event) =>
sharingConsentForm.setData(
'reason',
event.target.value,
)
}
/>
</Field>
<button
className={button}
disabled={sharingConsentForm.processing}
>
Revogar autorização de partilha
</button>
</form>
)}
</section>
<form
className="max-w-sm space-y-4"
onSubmit={(e) => {
e.preventDefault();
form.patch(`/management/interests/${interest.id}`);
}}
>
<Field label="Estado do pedido" error={form.errors.status}>
<select
className={control}
value={form.data.status}
onChange={(e) => form.setData('status', e.target.value)}
>
{[
'received',
'submitted',
'in_review',
'reviewed',
'qualification',
'qualified',
'contacted',
'contact_accepted',
'referred',
'due_diligence',
'negotiation',
'pending',
'refused',
'converted',
'cancelled',
'closed',
].map((key) => (
<option key={key} value={key}>
{states[key]}
</option>
))}
</select>
</Field>
<Field label="Nota de acompanhamento" error={form.errors.note}>
<textarea
className={control}
rows={3}
value={form.data.note}
onChange={(e) => form.setData('note', e.target.value)}
/>
</Field>
<Field label="Próxima acção" error={form.errors.next_action}>
<input
className={control}
value={form.data.next_action}
onChange={(e) =>
form.setData('next_action', e.target.value)
}
/>
</Field>
<Field
label="Prazo da próxima acção"
error={form.errors.next_action_due_at}
>
<input
type="date"
className={control}
value={form.data.next_action_due_at}
onChange={(e) =>
form.setData('next_action_due_at', e.target.value)
}
/>
</Field>
<Field label="Responsável" error={form.errors.assigned_to}>
<select
className={control}
value={form.data.assigned_to}
onChange={(e) =>
form.setData('assigned_to', e.target.value)
}
>
<option value="">Sem responsável</option>
{assignees.map((assignee) => (
<option key={assignee.id} value={assignee.id}>
{assignee.name}
</option>
))}
</select>
</Field>
<button className={button} disabled={form.processing}>
{form.processing ? 'A guardar…' : 'Guardar estado'}
</button>
</form>
<section className="space-y-3">
<h2 className="font-semibold">Histórico do fluxo</h2>
{!history.length && <p>Sem alterações de estado registadas.</p>}
{history.map((entry, index) => (
<article
key={`${entry.created_at}-${index}`}
className="rounded border p-3"
>
<p>
{entry.from_status
? `${states[entry.from_status] ?? entry.from_status} → `
: ''}
{states[entry.to_status] ?? entry.to_status} ·{' '}
{dateTime(entry.created_at)}
</p>
{entry.note && (
<p className="mt-1 whitespace-pre-wrap">
{entry.note}
</p>
)}
</article>
))}
</section>
</Management>
);
}