247 lines
9.4 KiB
TypeScript
247 lines
9.4 KiB
TypeScript
import { Link, useForm } from '@inertiajs/react';
|
|
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 form = useForm({ status: task.status });
|
|
return (
|
|
<form
|
|
className="space-y-3 rounded border p-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
form.patch(`/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={form.errors.status}
|
|
>
|
|
<select
|
|
className={control}
|
|
value={form.data.status}
|
|
onChange={(e) => form.setData('status', e.target.value)}
|
|
>
|
|
{Object.entries(taskStates).map(([key, label]) => (
|
|
<option key={key} value={key}>
|
|
{label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<button className={button} disabled={form.processing}>
|
|
Actualizar tarefa
|
|
</button>
|
|
</form>
|
|
);
|
|
}
|
|
export default function LeadDetail({
|
|
lead,
|
|
contacts,
|
|
owners,
|
|
}: {
|
|
lead: Lead;
|
|
contacts: {
|
|
id: string;
|
|
full_name: string;
|
|
email: string | null;
|
|
phone: string | null;
|
|
}[];
|
|
owners: Owner[];
|
|
}) {
|
|
const review = useForm({
|
|
status: lead.status,
|
|
owner_user_id: String(lead.owner_user_id ?? ''),
|
|
qualification_reason: '',
|
|
});
|
|
const task = useForm({
|
|
title: '',
|
|
due_at: '',
|
|
assignee_user_id: String(lead.owner_user_id ?? ''),
|
|
});
|
|
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>
|
|
<form
|
|
className="max-w-xl space-y-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
review.patch(`/management/crm/leads/${lead.id}`);
|
|
}}
|
|
>
|
|
<h2 className="text-lg font-semibold">
|
|
Qualificação e atribuição
|
|
</h2>
|
|
<Field label="Estado do lead" error={review.errors.status}>
|
|
<select
|
|
className={control}
|
|
value={review.data.status}
|
|
onChange={(e) =>
|
|
review.setData('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={review.errors.owner_user_id}>
|
|
<select
|
|
className={control}
|
|
value={review.data.owner_user_id}
|
|
onChange={(e) =>
|
|
review.setData('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={review.errors.qualification_reason}
|
|
>
|
|
<textarea
|
|
className={control}
|
|
value={review.data.qualification_reason}
|
|
onChange={(e) =>
|
|
review.setData(
|
|
'qualification_reason',
|
|
e.target.value,
|
|
)
|
|
}
|
|
/>
|
|
</Field>
|
|
{lead.qualification_reason && (
|
|
<p className="text-sm whitespace-pre-wrap">
|
|
Último fundamento: {lead.qualification_reason}
|
|
</p>
|
|
)}
|
|
<button
|
|
className={button}
|
|
disabled={review.processing || 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>
|
|
<form
|
|
className="max-w-xl space-y-4"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
task.transform((data) => ({
|
|
...data,
|
|
due_at: data.due_at
|
|
? new Date(data.due_at).toISOString()
|
|
: '',
|
|
}));
|
|
task.post(`/management/crm/leads/${lead.id}/tasks`, {
|
|
onSuccess: () => task.reset('title', 'due_at'),
|
|
});
|
|
}}
|
|
>
|
|
<h2 className="text-lg font-semibold">
|
|
Registar próxima acção
|
|
</h2>
|
|
<Field label="Tarefa" error={task.errors.title}>
|
|
<input
|
|
className={control}
|
|
value={task.data.title}
|
|
onChange={(e) => task.setData('title', e.target.value)}
|
|
/>
|
|
</Field>
|
|
<Field label="Prazo (hora local)" error={task.errors.due_at}>
|
|
<input
|
|
type="datetime-local"
|
|
className={control}
|
|
value={task.data.due_at}
|
|
onChange={(e) => task.setData('due_at', e.target.value)}
|
|
/>
|
|
</Field>
|
|
<Field
|
|
label="Responsável pela tarefa"
|
|
error={task.errors.assignee_user_id}
|
|
>
|
|
<select
|
|
className={control}
|
|
value={task.data.assignee_user_id}
|
|
onChange={(e) =>
|
|
task.setData('assignee_user_id', e.target.value)
|
|
}
|
|
>
|
|
<option value="">Seleccione</option>
|
|
{owners.map((owner) => (
|
|
<option key={owner.id} value={owner.id}>
|
|
{owner.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<button className={button} disabled={task.processing}>
|
|
Criar tarefa
|
|
</button>
|
|
</form>
|
|
</Management>
|
|
);
|
|
}
|