197 lines
8.0 KiB
TypeScript
197 lines
8.0 KiB
TypeScript
import { Link, useForm } from '@inertiajs/react';
|
|
import { Management, Pagination, button, control } from '../management/shared';
|
|
import type { Paginated } from '../management/shared';
|
|
import { leadStates } from './types';
|
|
import type { Lead, Owner } from './types';
|
|
|
|
export default function Leads({
|
|
leads,
|
|
filters,
|
|
owners,
|
|
originStatistics,
|
|
countries,
|
|
}: {
|
|
leads: Paginated<Lead>;
|
|
filters: {
|
|
q?: string;
|
|
status?: string;
|
|
owner_user_id?: number;
|
|
origin?: string;
|
|
};
|
|
owners: Owner[];
|
|
countries: { code: string; name: string }[];
|
|
originStatistics: {
|
|
interests: { code: string | null; name: string; count: number }[];
|
|
desk: { code: string | null; name: string; count: number }[];
|
|
businesses: { code: string | null; name: string; count: number }[];
|
|
};
|
|
}) {
|
|
const form = useForm({
|
|
q: filters.q ?? '',
|
|
status: filters.status ?? '',
|
|
owner_user_id: String(filters.owner_user_id ?? ''),
|
|
origin: filters.origin ?? '',
|
|
});
|
|
return (
|
|
<Management title="CRM — Leads">
|
|
<p>
|
|
Encaminhe uma manifestação de interesse para iniciar o
|
|
acompanhamento comercial.
|
|
</p>
|
|
<Link className="underline" href="/management/interests">
|
|
Consultar pedidos recebidos
|
|
</Link>
|
|
<Link className="underline" href="/balcao-virtual/painel">
|
|
Acompanhamento do investidor — pedidos, tarefas e respostas do
|
|
Balcão
|
|
</Link>
|
|
<details
|
|
open={Boolean(filters.origin)}
|
|
className="rounded border p-3"
|
|
>
|
|
<summary>Solicitações por origem declarada</summary>
|
|
<p>
|
|
Conta pedidos por origem declarada, sem inferir
|
|
nacionalidade. Manifestações e pedidos do Balcão são
|
|
conjuntos separados; não representam uma contagem de
|
|
investidores únicos.
|
|
</p>
|
|
<form
|
|
className="my-3 flex flex-wrap gap-3"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
form.get('/management/crm/leads', {
|
|
preserveState: false,
|
|
});
|
|
}}
|
|
>
|
|
<label className="max-w-full">
|
|
Origem das estatísticas
|
|
<select
|
|
aria-label="Origem das estatísticas"
|
|
className={`${control} max-w-full`}
|
|
value={form.data.origin}
|
|
onChange={(e) =>
|
|
form.setData('origin', e.target.value)
|
|
}
|
|
>
|
|
<option value="">Todas as origens</option>
|
|
<option value="unknown">Não informado</option>
|
|
{countries.map((country) => (
|
|
<option key={country.code} value={country.code}>
|
|
{country.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
<button className={button} disabled={form.processing}>
|
|
Filtrar estatísticas
|
|
</button>
|
|
</form>
|
|
<p className="text-sm">
|
|
Este filtro aplica-se apenas às contagens de solicitações. A
|
|
lista de leads mantém os filtros abaixo.
|
|
</p>
|
|
{(['interests', 'desk', 'businesses'] as const).map((group) => (
|
|
<section key={group}>
|
|
<h2>
|
|
{group === 'interests'
|
|
? 'Manifestações recebidas'
|
|
: group === 'desk'
|
|
? 'Pedidos do Balcão em acompanhamento'
|
|
: 'Negócios submetidos (rascunhos excluídos)'}
|
|
</h2>
|
|
{originStatistics[group].length ? (
|
|
<ul>
|
|
{originStatistics[group].map((row) => (
|
|
<li key={row.code ?? 'unknown'}>
|
|
{row.name}: {row.count}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : (
|
|
<p>Sem pedidos neste conjunto.</p>
|
|
)}
|
|
</section>
|
|
))}
|
|
</details>
|
|
<form
|
|
className="flex flex-wrap gap-3"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
form.get('/management/crm/leads', { preserveState: false });
|
|
}}
|
|
>
|
|
<input
|
|
aria-label="Pesquisar leads"
|
|
placeholder="Título ou investidor"
|
|
className={`${control} max-w-sm`}
|
|
value={form.data.q}
|
|
onChange={(e) => form.setData('q', e.target.value)}
|
|
/>
|
|
<select
|
|
aria-label="Estado"
|
|
className={`${control} max-w-xs`}
|
|
value={form.data.status}
|
|
onChange={(e) => form.setData('status', e.target.value)}
|
|
>
|
|
<option value="">Todos os estados</option>
|
|
{Object.entries(leadStates).map(([key, label]) => (
|
|
<option key={key} value={key}>
|
|
{label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<select
|
|
aria-label="Responsável"
|
|
className={`${control} max-w-xs`}
|
|
value={form.data.owner_user_id}
|
|
onChange={(e) =>
|
|
form.setData('owner_user_id', e.target.value)
|
|
}
|
|
>
|
|
<option value="">Todos os responsáveis</option>
|
|
{owners.map((owner) => (
|
|
<option key={owner.id} value={owner.id}>
|
|
{owner.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<button className={button} disabled={form.processing}>
|
|
Pesquisar
|
|
</button>
|
|
</form>
|
|
<div className="space-y-3">
|
|
{leads.data.map((lead) => (
|
|
<article
|
|
key={lead.id}
|
|
className="space-y-2 rounded border p-4"
|
|
>
|
|
<Link
|
|
className="font-semibold underline"
|
|
href={`/management/crm/leads/${lead.id}`}
|
|
>
|
|
{lead.title}
|
|
</Link>
|
|
<p>
|
|
{lead.investor?.legal_name ?? 'Sem investidor'} ·{' '}
|
|
{leadStates[lead.status]}
|
|
</p>
|
|
<p className="text-sm">
|
|
Responsável: {lead.owner?.name ?? 'Por atribuir'}
|
|
{Boolean(lead.overdue_tasks_count) && (
|
|
<span className="ml-3 text-red-600">
|
|
{lead.overdue_tasks_count} tarefa(s) em
|
|
atraso
|
|
</span>
|
|
)}
|
|
</p>
|
|
</article>
|
|
))}
|
|
</div>
|
|
{!leads.data.length && <p>Nenhum lead encontrado.</p>}
|
|
<Pagination page={leads} />
|
|
</Management>
|
|
);
|
|
}
|