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

257 lines
11 KiB
TypeScript

import { Link, router } from '@inertiajs/react';
import { useState } from 'react';
import { Management, Pagination, button, control, states } from './shared';
import type { Paginated } from './shared';
import CountrySelect, { type Country } from '@/components/country-select';
type Interest = {
id: number;
reference: string;
full_name: string;
email: string;
sector_interest: string;
existing_business_id: string | null;
modality: string | null;
assignee: { id: number; name: string } | null;
next_action: string | null;
next_action_due_at: string | null;
status: string;
countryName: string | null;
declaredOrigin: { code: string | null; name: string };
};
export default function Interests({
requests,
filters,
countries,
originStatistics,
}: {
requests: Paginated<Interest>;
filters: {
q?: string;
status?: string;
follow_up?: string;
country?: string;
origin?: string;
};
countries: Country[];
originStatistics: Record<
string,
{ code: string | null; name: string; count: number }[]
>;
}) {
const [q, setQ] = useState(filters.q ?? '');
const [status, setStatus] = useState(filters.status ?? '');
const [followUp, setFollowUp] = useState(filters.follow_up ?? '');
const [country, setCountry] = useState(filters.country ?? '');
const [origin, setOrigin] = useState(filters.origin ?? '');
return (
<Management title="Manifestações de interesse">
<form
className="flex flex-wrap gap-3"
onSubmit={(e) => {
e.preventDefault();
router.get('/management/interests', {
q,
status,
follow_up: followUp,
country,
origin,
});
}}
>
<input
aria-label="Pesquisar pedidos"
className={`${control} max-w-sm`}
placeholder="Referência, nome ou email"
value={q}
onChange={(e) => setQ(e.target.value)}
/>
<select
aria-label="Estado"
className={`${control} max-w-xs`}
value={status}
onChange={(e) => setStatus(e.target.value)}
>
<option value="">Todos os estados</option>
{Object.keys(states)
.filter((key) =>
[
'received',
'submitted',
'in_review',
'reviewed',
'qualification',
'qualified',
'contacted',
'contact_accepted',
'referred',
'due_diligence',
'negotiation',
'pending',
'refused',
'converted',
'cancelled',
'closed',
].includes(key),
)
.map((key) => (
<option key={key} value={key}>
{states[key]}
</option>
))}
</select>
<select
aria-label="Acompanhamento"
className={`${control} max-w-xs`}
value={followUp}
onChange={(e) => setFollowUp(e.target.value)}
>
<option value="">Todos os acompanhamentos</option>
<option value="overdue">Acções atrasadas</option>
<option value="upcoming">Acções por vencer</option>
<option value="unassigned">Sem responsável</option>
</select>
<CountrySelect
countries={countries}
value={country}
onChange={setCountry}
label="Filtrar país do requerente"
/>
<label>
Origem declarada
<select
aria-label="Filtrar origem declarada"
className={control}
value={origin}
onChange={(e) => setOrigin(e.target.value)}
>
<option value="">Todas as origens</option>
<option value="unknown">Não informada</option>
{countries.map((c) => (
<option key={c.code} value={c.code}>
{c.name} ({c.code})
</option>
))}
</select>
</label>
<button className={button}>Pesquisar</button>
</form>
<div className="overflow-x-auto">
<table className="w-full text-left text-sm">
<thead>
<tr>
{[
'Referência',
'Nome',
'Email',
'País do requerente',
'Origem declarada',
'Sector',
'Modalidade',
'Responsável',
'Próxima acção',
'Prazo',
'Estado',
].map((label) => (
<th key={label} className="border-b p-3">
{label}
</th>
))}
</tr>
</thead>
<tbody>
{requests.data.map((item) => (
<tr key={item.id}>
<td className="border-b p-3">
<Link
className="underline"
href={`/management/interests/${item.id}`}
>
{item.reference}
</Link>
</td>
<td className="border-b p-3">
{item.full_name}
</td>
<td className="border-b p-3">{item.email}</td>
<td className="border-b p-3">
{item.countryName ?? 'Não informado'}
</td>
<td className="border-b p-3">
{item.declaredOrigin.name}
</td>
<td className="border-b p-3">
{item.sector_interest}
</td>
<td className="border-b p-3">
{item.modality ??
(item.existing_business_id
? 'Negócio'
: '—')}
</td>
<td className="border-b p-3">
{item.assignee?.name ?? '—'}
</td>
<td className="border-b p-3">
{item.next_action ?? '—'}
</td>
<td
className={`border-b p-3 ${item.next_action_due_at && new Date(item.next_action_due_at) < new Date() ? 'font-semibold text-red-700' : ''}`}
>
{item.next_action_due_at
? `${new Date(item.next_action_due_at) < new Date() ? 'Atrasada · ' : ''}${new Intl.DateTimeFormat('pt-MZ', { dateStyle: 'medium', timeZone: 'Africa/Maputo' }).format(new Date(item.next_action_due_at))}`
: '—'}
</td>
<td className="border-b p-3">
{states[item.status] ?? item.status}
</td>
</tr>
))}
</tbody>
</table>
</div>
{!requests.data.length && <p>Nenhum pedido encontrado.</p>}
<Pagination page={requests} />
<details>
<summary>
Estatísticas de solicitações por origem declarada
</summary>
<p>
Contagens por conjunto e filtro de origem; o filtro do país
do requerente aplica-se à lista. Rascunhos de negócios e do
Balcão são excluídos. Apoio fundiário recebido integra
manifestações; não se soma como outro conjunto nem se contam
investidores únicos.
</p>
{Object.entries(originStatistics).map(([group, counts]) => (
<section key={group}>
<h2>
{
(
{
interests:
'Manifestações recebidas, incluindo apoio fundiário',
desk: 'Pedidos do Balcão',
businesses: 'Negócios submetidos',
} as Record<string, string>
)[group]
}
</h2>
{counts.length ? (
<ul>
{counts.map((row) => (
<li key={row.code ?? 'unknown'}>
{row.name}: {row.count}
</li>
))}
</ul>
) : (
<p>Sem solicitações neste conjunto.</p>
)}
</section>
))}
</details>
</Management>
);
}