274 lines
10 KiB
TypeScript
274 lines
10 KiB
TypeScript
import CrmTaskCreation from '@/components/crm-task-creation';
|
|
import type { TaskCreationContext } from '@/lib/crm-task-creation';
|
|
import { Link } from '@inertiajs/react';
|
|
import { useCrmRecordEdit } from '@/lib/crm-record-edit';
|
|
import CrmEditComparison from '@/components/crm-edit-comparison';
|
|
import { Management, Field, button, control } from '../management/shared';
|
|
import { leadStates, taskStates } from './types';
|
|
import type { Lead, Owner, Task } from './types';
|
|
|
|
function TaskRow({ task, leadId }: { task: Task; leadId: string }) {
|
|
const edit = useCrmRecordEdit({
|
|
id: task.id,
|
|
kind: 'task',
|
|
version: task.version_no,
|
|
values: { status: task.status },
|
|
});
|
|
const form = edit.form;
|
|
return (
|
|
<form
|
|
id={`crm-task-${task.id}`}
|
|
className="space-y-3 rounded border p-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
if (task.can_update === false) return;
|
|
edit.submit(`/management/crm/leads/${leadId}/tasks/${task.id}`);
|
|
}}
|
|
>
|
|
<h3 className="font-semibold">{task.title}</h3>
|
|
<p>
|
|
{task.due_at
|
|
? new Intl.DateTimeFormat('pt-MZ', {
|
|
dateStyle: 'medium',
|
|
timeStyle: 'short',
|
|
timeZone: 'Africa/Maputo',
|
|
}).format(new Date(task.due_at)) + ' (Maputo)'
|
|
: 'Sem prazo'}{' '}
|
|
· Responsável #{task.assignee_user_id}
|
|
</p>
|
|
<Field
|
|
label={`Estado da tarefa: ${task.title}`}
|
|
error={edit.error('status')}
|
|
>
|
|
<select
|
|
className={control}
|
|
disabled={task.can_update === false}
|
|
value={edit.values.status}
|
|
onChange={(e) => edit.change('status', e.target.value)}
|
|
>
|
|
{Object.entries(taskStates).map(([key, label]) => (
|
|
<option key={key} value={key}>
|
|
{label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
{task.can_update !== false ? (
|
|
<>
|
|
<CrmEditComparison
|
|
editor={edit}
|
|
labels={{ status: 'Estado da tarefa' }}
|
|
display={(_key, value) => taskStates[value] ?? value}
|
|
/>
|
|
<button
|
|
className={button}
|
|
disabled={form.processing || edit.blocked}
|
|
>
|
|
Actualizar tarefa
|
|
</button>
|
|
</>
|
|
) : (
|
|
<p>
|
|
Dossier associado encerrado; tarefa disponível para
|
|
consulta.
|
|
</p>
|
|
)}
|
|
</form>
|
|
);
|
|
}
|
|
export default function LeadDetail({
|
|
lead,
|
|
contacts,
|
|
owners,
|
|
nextAction,
|
|
taskCreation,
|
|
}: {
|
|
lead: Lead;
|
|
contacts: {
|
|
id: string;
|
|
full_name: string;
|
|
email: string | null;
|
|
phone: string | null;
|
|
}[];
|
|
owners: Owner[];
|
|
nextAction?: { future_tasks: number };
|
|
taskCreation: TaskCreationContext;
|
|
}) {
|
|
const edit = useCrmRecordEdit({
|
|
id: lead.id,
|
|
kind: 'lead',
|
|
version: lead.version_no,
|
|
blankAfterSave: ['qualification_reason'],
|
|
values: {
|
|
status: lead.status,
|
|
owner_user_id: String(lead.owner_user_id ?? ''),
|
|
qualification_reason: '',
|
|
},
|
|
});
|
|
const review = edit.form;
|
|
return (
|
|
<Management title={lead.title}>
|
|
<Link className="underline" href="/management/crm/leads">
|
|
Voltar aos leads
|
|
</Link>
|
|
<section className="space-y-2">
|
|
<h2 className="text-xl font-semibold">
|
|
{lead.investor && (
|
|
<Link
|
|
className="underline"
|
|
href={`/management/crm/investors/${lead.investor.id}`}
|
|
>
|
|
{lead.investor.legal_name}
|
|
</Link>
|
|
)}
|
|
</h2>
|
|
<p>
|
|
{leadStates[lead.status]} ·{' '}
|
|
{lead.owner?.name ?? 'Sem responsável'}
|
|
</p>
|
|
<p>Origem inicial: {lead.first_source ?? 'Não registada'}</p>
|
|
{lead.interest_request_id && (
|
|
<Link
|
|
className="underline"
|
|
href={`/management/interests/${lead.interest_request_id}`}
|
|
>
|
|
Consultar pedido de origem
|
|
</Link>
|
|
)}
|
|
<div className="space-y-2">
|
|
{contacts.map((contact) => (
|
|
<p key={contact.id}>
|
|
{contact.full_name} · {contact.email ?? '—'} ·{' '}
|
|
{contact.phone ?? '—'}
|
|
</p>
|
|
))}
|
|
</div>
|
|
</section>
|
|
{nextAction && (
|
|
<section
|
|
aria-label="Próxima acção"
|
|
className="space-y-2 rounded border p-4"
|
|
>
|
|
<p>
|
|
{nextAction.future_tasks === 1
|
|
? '1 tarefa disponível com prazo futuro.'
|
|
: `${nextAction.future_tasks} tarefas disponíveis com prazo futuro.`}
|
|
</p>
|
|
{nextAction.future_tasks === 0 &&
|
|
['new', 'qualified'].includes(lead.status) && (
|
|
<p role="status">
|
|
Registe uma próxima acção com prazo futuro antes
|
|
de qualificar o acompanhamento ou concluir a sua
|
|
última tarefa. Tarefas num dossier encerrado
|
|
ficam no histórico e não contam como próxima
|
|
acção.
|
|
</p>
|
|
)}
|
|
<a className="underline" href="#new-crm-task">
|
|
Registar próxima acção
|
|
</a>
|
|
</section>
|
|
)}
|
|
<form
|
|
className="max-w-xl space-y-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
edit.submit(`/management/crm/leads/${lead.id}`);
|
|
}}
|
|
>
|
|
<h2 className="text-lg font-semibold">
|
|
Qualificação e atribuição
|
|
</h2>
|
|
<Field label="Estado do lead" error={edit.error('status')}>
|
|
<select
|
|
className={control}
|
|
value={edit.values.status}
|
|
onChange={(e) => edit.change('status', e.target.value)}
|
|
>
|
|
{Object.entries(leadStates)
|
|
.filter(([key]) => key !== 'converted')
|
|
.map(([key, label]) => (
|
|
<option key={key} value={key}>
|
|
{label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field label="Responsável" error={edit.error('owner_user_id')}>
|
|
<select
|
|
className={control}
|
|
value={edit.values.owner_user_id}
|
|
onChange={(e) =>
|
|
edit.change('owner_user_id', e.target.value)
|
|
}
|
|
>
|
|
<option value="">Seleccione</option>
|
|
{owners.map((owner) => (
|
|
<option key={owner.id} value={owner.id}>
|
|
{owner.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field
|
|
label="Fundamento da alteração"
|
|
error={edit.error('qualification_reason')}
|
|
>
|
|
<textarea
|
|
className={control}
|
|
value={edit.values.qualification_reason}
|
|
onChange={(e) =>
|
|
edit.change('qualification_reason', e.target.value)
|
|
}
|
|
/>
|
|
</Field>
|
|
{lead.qualification_reason && (
|
|
<p className="text-sm whitespace-pre-wrap">
|
|
Último fundamento: {lead.qualification_reason}
|
|
</p>
|
|
)}
|
|
<CrmEditComparison
|
|
editor={edit}
|
|
labels={{
|
|
status: 'Estado do lead',
|
|
owner_user_id: 'Responsável',
|
|
qualification_reason: 'Fundamento da alteração',
|
|
}}
|
|
display={(key, value) =>
|
|
key === 'status'
|
|
? (leadStates[value] ?? value)
|
|
: key === 'owner_user_id'
|
|
? (owners.find(
|
|
(owner) => String(owner.id) === value,
|
|
)?.name ?? 'Responsável por confirmar')
|
|
: value
|
|
}
|
|
/>
|
|
<button
|
|
className={button}
|
|
disabled={
|
|
review.processing ||
|
|
edit.blocked ||
|
|
lead.status === 'converted'
|
|
}
|
|
>
|
|
Guardar lead
|
|
</button>
|
|
</form>
|
|
<section className="space-y-4">
|
|
<h2 className="text-lg font-semibold">Tarefas</h2>
|
|
{lead.tasks.map((item) => (
|
|
<TaskRow key={item.id} task={item} leadId={lead.id} />
|
|
))}
|
|
{!lead.tasks.length && <p>Não existem tarefas.</p>}
|
|
</section>
|
|
<CrmTaskCreation
|
|
key={`${lead.id}:${taskCreation.actor_id}`}
|
|
lead={lead}
|
|
owners={owners}
|
|
context={taskCreation}
|
|
/>
|
|
</Management>
|
|
);
|
|
}
|