122 lines
4.5 KiB
TypeScript
122 lines
4.5 KiB
TypeScript
import { Head, Link, usePage } from "@inertiajs/react";
|
|
import type { ReactNode } from "react";
|
|
import { cloneElement, isValidElement, useId } from "react";
|
|
|
|
export type Paginated<T> = {
|
|
data: T[];
|
|
total: number;
|
|
current_page: number;
|
|
last_page: number;
|
|
prev_page_url: string | null;
|
|
next_page_url: string | null;
|
|
};
|
|
export const control = "w-full rounded-md border border-input bg-background p-2 text-foreground";
|
|
export const button =
|
|
"inline-flex items-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground disabled:opacity-50";
|
|
export const states: Record<string, string> = {
|
|
received: "Recebido",
|
|
in_review: "Em análise",
|
|
contacted: "Contactado",
|
|
closed: "Encerrado",
|
|
draft: "Rascunho",
|
|
published: "Publicado",
|
|
archived: "Arquivado",
|
|
};
|
|
|
|
export function Management({ title, children }: { title: string; children: ReactNode }) {
|
|
const { flash, educationEnabled, aiEnabled } = usePage<{
|
|
flash?: { success?: string };
|
|
educationEnabled?: boolean;
|
|
aiEnabled?: boolean;
|
|
}>().props;
|
|
return (
|
|
<main className="space-y-6 p-4 md:p-8">
|
|
<Head title={title} />
|
|
<nav className="flex flex-wrap gap-4 text-sm underline" aria-label="Gestão do portal">
|
|
<Link href="/management/interests">Pedidos de interesse</Link>
|
|
<Link href="/management/sectors">Sectores</Link>
|
|
<Link href="/management/opportunities">Oportunidades</Link>
|
|
<Link href="/management/history">Histórico</Link>
|
|
<Link href="/management/crm/leads">CRM</Link>
|
|
<Link href="/management/crm/investors">Investidores</Link>
|
|
<Link href="/management/projects">Projectos</Link>
|
|
<Link href="/management/clusters">Clusters</Link>
|
|
<Link href="/management/territory-associations">
|
|
Localizações dos investimentos
|
|
</Link>
|
|
<Link href="/management/investment/zones">ZEE, ZFI e parques</Link>
|
|
{educationEnabled && (
|
|
<Link href="/management/education">Competências e formação</Link>
|
|
)}
|
|
{aiEnabled && <Link href="/management/ai">Assistência e fontes</Link>}
|
|
<Link href="/management/portfolio">Corredores e matriz de investimento</Link>
|
|
<Link href="/management/portal-services">Documentos e parcerias</Link>
|
|
<Link href="/management/investment/references">Referências e formulários</Link>
|
|
<a href="/">Portal público</a>
|
|
</nav>
|
|
<h1 className="text-2xl font-semibold break-words">{title}</h1>
|
|
{flash?.success && (
|
|
<p role="status" className="rounded border border-emerald-600 p-3">
|
|
{flash.success}
|
|
</p>
|
|
)}
|
|
{children}
|
|
</main>
|
|
);
|
|
}
|
|
|
|
export function Pagination<T>({ page }: { page: Paginated<T> }) {
|
|
return (
|
|
<nav aria-label="Paginação" className="flex flex-wrap items-center gap-4">
|
|
{page.prev_page_url && (
|
|
<Link className={button} href={page.prev_page_url}>
|
|
Anterior
|
|
</Link>
|
|
)}
|
|
<span>
|
|
{page.total} registos · Página {page.current_page} de {page.last_page}
|
|
</span>
|
|
{page.next_page_url && (
|
|
<Link className={button} href={page.next_page_url}>
|
|
Seguinte
|
|
</Link>
|
|
)}
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
export function Field({
|
|
label,
|
|
error,
|
|
children,
|
|
}: {
|
|
label: string;
|
|
error?: string;
|
|
children: ReactNode;
|
|
}) {
|
|
const id = useId();
|
|
return (
|
|
<label className="block space-y-1">
|
|
<span id={id} className="text-sm font-medium">
|
|
{label}
|
|
</span>
|
|
{isValidElement<{
|
|
"aria-labelledby"?: string;
|
|
"aria-describedby"?: string;
|
|
"aria-invalid"?: boolean;
|
|
}>(children)
|
|
? cloneElement(children, {
|
|
"aria-labelledby": id,
|
|
"aria-describedby": error ? `${id}-error` : undefined,
|
|
"aria-invalid": Boolean(error),
|
|
})
|
|
: children}
|
|
{error && (
|
|
<span id={`${id}-error`} role="alert" className="block text-sm text-red-600">
|
|
{error}
|
|
</span>
|
|
)}
|
|
</label>
|
|
);
|
|
}
|