163 lines
6.3 KiB
TypeScript
163 lines
6.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import Link from "next/link";
|
|
import LinkEntityModal from "@/components/LinkEntityModal";
|
|
import StatusBadge from "@/components/StatusBadge";
|
|
import { text, formatDate, deriveCompanyStatus } from "@/lib/format";
|
|
import type { YsterbaardCompany } from "@/lib/types";
|
|
|
|
interface JunctionEntry {
|
|
id: string;
|
|
company_id: string;
|
|
company: YsterbaardCompany;
|
|
}
|
|
|
|
interface Props {
|
|
directorId: string;
|
|
directorName: string;
|
|
initial: JunctionEntry[];
|
|
}
|
|
|
|
export default function LinkedCompaniesList({ directorId, directorName, initial }: Props) {
|
|
const router = useRouter();
|
|
const [showLinkModal, setShowLinkModal] = useState(false);
|
|
const [unlinking, setUnlinking] = useState<string | null>(null);
|
|
const [linked, setLinked] = useState<JunctionEntry[]>(initial);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const alreadyLinkedIds = linked.map((j) => j.company_id);
|
|
|
|
async function handleUnlink(junctionId: string, companyName: string) {
|
|
if (!confirm(`Remove link to "${companyName}"? The company will remain in PocketBase.`)) return;
|
|
setUnlinking(junctionId);
|
|
setError(null);
|
|
try {
|
|
const res = await fetch("/api/pb/records/link", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
action: "unlink",
|
|
director: directorId,
|
|
company: linked.find((j) => j.id === junctionId)?.company_id,
|
|
}),
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json();
|
|
throw new Error(err.error ?? "Unlink failed");
|
|
}
|
|
setLinked((prev) => prev.filter((j) => j.id !== junctionId));
|
|
window.location.reload();
|
|
} catch (e: unknown) {
|
|
setError(e instanceof Error ? e.message : "Unlink failed");
|
|
} finally {
|
|
setUnlinking(null);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="flex flex-col gap-3 min-w-0">
|
|
<div className="flex items-baseline justify-between gap-3 flex-wrap">
|
|
<h2 className="text-xl font-semibold text-fg">Linked companies</h2>
|
|
<div className="flex items-center gap-3">
|
|
<p className="text-sm text-muted">
|
|
{linked.length} record{linked.length === 1 ? "" : "s"}
|
|
</p>
|
|
<button
|
|
onClick={() => setShowLinkModal(true)}
|
|
className="px-3 py-1.5 rounded-md bg-accent text-accent-on text-xs font-bold hover:bg-accent-hover transition-colors"
|
|
>
|
|
+ Link company
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="px-4 py-2.5 rounded-lg bg-red-500/10 border border-red-500/30 text-red-400 text-sm">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{linked.length === 0 ? (
|
|
<div className="bg-surface border border-border rounded-lg p-10 text-center text-muted">
|
|
No companies linked to this director.
|
|
</div>
|
|
) : (
|
|
<div className="flex flex-col gap-3">
|
|
{linked.map((j) => {
|
|
const c = j.company;
|
|
const bucket = deriveCompanyStatus(c.enterprise_status, c.is_active);
|
|
const badge = (() => {
|
|
if (bucket === "active") return <StatusBadge variant="active">In business</StatusBadge>;
|
|
if (bucket === "deregistered") return <StatusBadge variant="deregistered">Deregistered</StatusBadge>;
|
|
if (bucket === "in_process") return <StatusBadge variant="in_process">In process</StatusBadge>;
|
|
return <StatusBadge variant="inactive">Inactive</StatusBadge>;
|
|
})();
|
|
|
|
return (
|
|
<div
|
|
key={j.id}
|
|
className="bg-surface border border-border rounded-lg p-5 flex flex-col gap-3 hover:bg-surface-warm transition-colors"
|
|
>
|
|
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
|
|
<div className="min-w-0">
|
|
<p className="text-base font-semibold text-fg break-words">
|
|
{text(c.enterprise_name)}
|
|
</p>
|
|
<p className="text-sm text-muted font-mono mt-1">
|
|
{text(c.enterprise_no)}
|
|
</p>
|
|
</div>
|
|
<div className="flex gap-2 flex-wrap items-center">
|
|
{badge}
|
|
{text(c.director_status) !== "—" && (
|
|
<StatusBadge variant="muted">{text(c.director_status)}</StatusBadge>
|
|
)}
|
|
<button
|
|
onClick={() => handleUnlink(j.id, text(c.enterprise_name))}
|
|
disabled={unlinking !== null}
|
|
title="Unlink company"
|
|
className="text-xs px-2.5 py-1 rounded border border-red-500/30 bg-red-500/10 text-red-400 hover:bg-red-500/20 transition-colors disabled:opacity-40"
|
|
>
|
|
{unlinking === j.id ? "…" : "Unlink"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-wrap gap-x-6 gap-y-2 text-sm text-muted">
|
|
<span>
|
|
Status: <span className="text-fg-2">{text(c.enterprise_status)}</span>
|
|
</span>
|
|
<span>
|
|
Source: <span className="text-fg-2">{text(c.source)}</span>
|
|
</span>
|
|
<span>
|
|
Last checked: <span className="text-fg-2">{formatDate(c.last_checked)}</span>
|
|
</span>
|
|
</div>
|
|
<Link
|
|
href={`/companies/${c.id}`}
|
|
className="text-xs font-semibold text-accent hover:underline self-start"
|
|
>
|
|
View company →
|
|
</Link>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<LinkEntityModal
|
|
mode="link-director"
|
|
parentId={directorId}
|
|
parentName={directorName}
|
|
alreadyLinkedIds={alreadyLinkedIds}
|
|
open={showLinkModal}
|
|
onClose={() => setShowLinkModal(false)}
|
|
onLinked={() => router.refresh()}
|
|
/>
|
|
</>
|
|
);
|
|
}
|