Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/assets/texts.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"crossmatch.triage.pending": "Pending",
"crossmatch.triage.resolved": "Resolved",
"crossmatch.triage.verbose.pending": "Pending manual check",
"crossmatch.triage.verbose.resolved": "Resolved"
"crossmatch.triage.verbose.resolved": "Resolved",
"crossmatch.action.mark_new": "Mark as new",
"crossmatch.action.resolve": "Match to this PGC"
}
}
73 changes: 19 additions & 54 deletions src/pages/CrossmatchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { getCrossmatchRecords } from "../clients/admin/sdk.gen";
import type {
GetRecordsCrossmatchResponse,
RecordCrossmatch,
RecordCrossmatchStatus,
RecordTriageStatus,
ValidationError,
} from "../clients/admin/types.gen";
Expand All @@ -27,45 +26,35 @@ import { adminClient } from "../clients/config";

interface CrossmatchFiltersProps {
tableName: string | null;
status: RecordCrossmatchStatus | null;
triageStatus: string | null;
pageSize: number;
onApplyFilters: (
tableName: string,
status: string,
triageStatus: string,
pageSize: number,
) => void;
}

function CrossmatchFilters({
tableName,
status,
triageStatus,
pageSize,
onApplyFilters,
}: CrossmatchFiltersProps): ReactElement {
const [localStatus, setLocalStatus] = useState<string>(status || "all");
const [localTriageStatus, setLocalTriageStatus] = useState<string>(
triageStatus ?? "pending",
);
const [localPageSize, setLocalPageSize] = useState<number>(pageSize);
const [localTableName, setLocalTableName] = useState<string>(tableName || "");

useEffect(() => {
setLocalStatus(status || "all");
setLocalTriageStatus(triageStatus ?? "pending");
setLocalPageSize(pageSize);
setLocalTableName(tableName || "");
}, [status, triageStatus, pageSize, tableName]);
}, [triageStatus, pageSize, tableName]);

function applyFilters(): void {
onApplyFilters(
localTableName,
localStatus,
localTriageStatus,
localPageSize,
);
onApplyFilters(localTableName, localTriageStatus, localPageSize);
}

return (
Expand All @@ -78,18 +67,6 @@ function CrossmatchFilters({
onEnter={applyFilters}
/>
<Link href={`/table/${localTableName.trim()}`} external />
<DropdownFilter
title="Status"
options={[
{ value: "all", label: "All Statuses" },
{ value: "unprocessed", label: "Unprocessed" },
{ value: "new", label: "New" },
{ value: "collided", label: "Collided" },
{ value: "existing", label: "Existing" },
]}
value={localStatus}
onChange={setLocalStatus}
/>
<DropdownFilter
title="Manual check status"
options={[
Expand Down Expand Up @@ -127,12 +104,23 @@ function CrossmatchResults({
data,
loading,
}: CrossmatchResultsProps): ReactElement {
function getTriageStatusLabel(triageStatus: RecordTriageStatus): string {
return getResource(`crossmatch.triage.${triageStatus}`).Title;
}

function getRecordName(record: RecordCrossmatch): ReactElement {
const displayName = record.catalogs.designation?.name || record.record_id;
const triageStatusLabel = getTriageStatusLabel(record.triage_status);
const triageBadgeType =
record.triage_status === "resolved" ? "success" : "warning";

return (
<Link href={`/records/${record.record_id}/crossmatch`}>
{displayName}
</Link>
<div className="flex items-center gap-2">
<Link href={`/records/${record.record_id}/crossmatch`}>
{displayName}
</Link>
<Badge type={triageBadgeType}>{triageStatusLabel}</Badge>
</div>
);
}

Expand All @@ -156,14 +144,6 @@ function CrossmatchResults({
);
}

function getStatusLabel(status: RecordCrossmatchStatus): string {
return getResource(`crossmatch.status.${status}`).Title;
}

function getTriageStatusLabel(triageStatus: RecordTriageStatus): string {
return getResource(`crossmatch.triage.${triageStatus}`).Title;
}

const columns: Column[] = [
{
name: "Record name",
Expand All @@ -174,8 +154,6 @@ function CrossmatchResults({
return <span>NULL</span>;
},
},
{ name: "Status" },
{ name: "Manual check status" },
{
name: "Candidates",
renderCell: (recordIndex: CellPrimitive) => {
Expand All @@ -188,10 +166,8 @@ function CrossmatchResults({
];

const tableData: Record<string, CellPrimitive>[] =
data?.records.map((record: RecordCrossmatch, index: number) => ({
data?.records.map((_: RecordCrossmatch, index: number) => ({
"Record name": index,
Status: getStatusLabel(record.status),
"Manual check status": getTriageStatusLabel(record.triage_status),
Candidates: index,
})) || [];

Expand All @@ -200,7 +176,6 @@ function CrossmatchResults({

async function fetcher(
tableName: string | null,
status: RecordCrossmatchStatus | null,
triageStatus: RecordTriageStatus | null,
page: number,
pageSize: number,
Expand All @@ -213,7 +188,6 @@ async function fetcher(
client: adminClient,
query: {
table_name: tableName,
status: status,
triage_status: triageStatus,
page: page,
page_size: pageSize,
Expand All @@ -239,7 +213,6 @@ export function CrossmatchResultsPage(): ReactElement {
const [searchParams, setSearchParams] = useSearchParams();

const tableName = searchParams.get("table_name");
const status = searchParams.get("status") as RecordCrossmatchStatus | null;
const triageStatusParam = searchParams.get("triage_status");
const apiTriageStatus: RecordTriageStatus | null =
triageStatusParam === null || triageStatusParam === ""
Expand All @@ -255,8 +228,8 @@ export function CrossmatchResultsPage(): ReactElement {
}, [tableName]);

const { data, loading, error } = useDataFetching(
() => fetcher(tableName, status, apiTriageStatus, page, pageSize),
[tableName, status, apiTriageStatus, page, pageSize],
() => fetcher(tableName, apiTriageStatus, page, pageSize),
[tableName, apiTriageStatus, page, pageSize],
);

function handlePageChange(newPage: number): void {
Expand All @@ -267,7 +240,6 @@ export function CrossmatchResultsPage(): ReactElement {

function handleApplyFilters(
newTableName: string,
newStatus: string,
newTriageStatus: string,
newPageSize: number,
): void {
Expand All @@ -279,12 +251,6 @@ export function CrossmatchResultsPage(): ReactElement {
newSearchParams.delete("table_name");
}

if (newStatus === "all") {
newSearchParams.delete("status");
} else {
newSearchParams.set("status", newStatus);
}

if (newTriageStatus === "all") {
newSearchParams.set("triage_status", "all");
} else {
Expand Down Expand Up @@ -320,7 +286,6 @@ export function CrossmatchResultsPage(): ReactElement {
<h2 className="text-3xl font-bold mb-4">Crossmatch results</h2>
<CrossmatchFilters
tableName={tableName}
status={status}
triageStatus={triageStatusParam}
pageSize={pageSize}
onApplyFilters={handleApplyFilters}
Expand Down
96 changes: 67 additions & 29 deletions src/pages/RecordCrossmatchDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
RecordCrossmatch,
PgcCandidate,
Schema as AdminSchema,
StatusesPayload,
} from "../clients/admin/types.gen";
import { Schema as BackendSchema } from "../clients/backend/types.gen";
import { getResource } from "../resources/resources";
Expand Down Expand Up @@ -151,7 +152,7 @@ function OriginalData({
function RecordCrossmatchDetails({
data,
}: RecordCrossmatchDetailsProps): ReactElement {
const [resolvingPgc, setResolvingPgc] = useState<number | null>(null);
const [resolving, setResolving] = useState<"new" | number | null>(null);
const [resolveError, setResolveError] = useState<string | null>(null);
const {
crossmatch,
Expand All @@ -174,36 +175,57 @@ function RecordCrossmatchDetails({
`crossmatch.triage.verbose.${crossmatch.triage_status}`,
).Title;

async function submitCrossmatchResolution(
statuses: StatusesPayload,
): Promise<void> {
const response = await setCrossmatchResults({
client: adminClient,
body: { statuses },
});

if (response.error || !response.data?.data) {
throw new Error(
typeof response.error === "object"
? JSON.stringify(response.error)
: String(response.error || "Unknown error"),
);
}

window.location.reload();
}

async function resolveCandidate(pgc: number): Promise<void> {
setResolveError(null);
setResolvingPgc(pgc);
setResolving(pgc);
try {
const response = await setCrossmatchResults({
client: adminClient,
body: {
statuses: {
existing: {
record_ids: [crossmatch.record_id],
pgcs: [pgc],
triage_statuses: ["resolved"],
},
},
await submitCrossmatchResolution({
existing: {
record_ids: [crossmatch.record_id],
pgcs: [pgc],
triage_statuses: ["resolved"],
},
});
} catch (err) {
setResolveError(`${err}`);
} finally {
setResolving(null);
}
}

if (response.error || !response.data?.data) {
throw new Error(
typeof response.error === "object"
? JSON.stringify(response.error)
: String(response.error || "Unknown error"),
);
}

window.location.reload();
async function markAsNew(): Promise<void> {
setResolveError(null);
setResolving("new");
try {
await submitCrossmatchResolution({
new: {
record_ids: [crossmatch.record_id],
triage_statuses: ["resolved"],
},
});
} catch (err) {
setResolveError(`${err}`);
} finally {
setResolvingPgc(null);
setResolving(null);
}
}

Expand Down Expand Up @@ -243,6 +265,19 @@ function RecordCrossmatchDetails({
? "1 candidate"
: `${candidates.length} candidates`}
</p>
{showResolveControls && (
<div className="mt-4 flex flex-wrap gap-2">
<Button
type="button"
disabled={resolving !== null}
onClick={() => markAsNew()}
>
{resolving === "new"
? "Saving…"
: getResource("crossmatch.action.mark_new").Title}
</Button>
</div>
)}
</div>
</div>

Expand All @@ -254,14 +289,15 @@ function RecordCrossmatchDetails({
</Accordion>
)}

{resolveError && (
<p className="text-red-400 text-sm" role="alert">
{resolveError}
</p>
)}

{candidates.length > 0 && (
<div className="space-y-6">
<h2 className="text-xl font-bold">Crossmatch Candidates</h2>
{resolveError && (
<p className="text-red-400 text-sm" role="alert">
{resolveError}
</p>
)}
{candidates.map((candidate) => (
<Accordion
key={candidate.pgc}
Expand All @@ -279,10 +315,12 @@ function RecordCrossmatchDetails({
{showResolveControls && (
<Button
type="button"
disabled={resolvingPgc !== null}
disabled={resolving !== null}
onClick={() => resolveCandidate(candidate.pgc)}
>
{resolvingPgc === candidate.pgc ? "Resolving…" : "Resolve"}
{resolving === candidate.pgc
? "Saving…"
: getResource("crossmatch.action.resolve").Title}
</Button>
)}
</div>
Expand Down
Loading