Files
Investidor-V2/resources/js/pages/crm/lead.tsx
T

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>
);
}