Files
Investidor-V2/resources/js/pages/crm/leads.tsx
T
nala 6ddafc95ba
tests / ci (push) Canceled after 0s
oportunidades 4
2026-10-10 07:03:59 +02:00

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