251 lines
9.2 KiB
TypeScript
251 lines
9.2 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useCallback, useMemo, useState } from "react";
|
|
import { GitFork } from "lucide-react";
|
|
import { BrapiEntityPage, type BrapiFormField } from "@/components/brapi/BrapiEntityPage";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import {
|
|
CROSS_TYPE_OPTIONS,
|
|
PLANNED_CROSS_STATUS_OPTIONS,
|
|
crossTypeLabel,
|
|
plannedStatusLabel,
|
|
} from "../constants";
|
|
import {
|
|
createCrossRow,
|
|
createPlannedCrossRow,
|
|
normalizeCrossForm,
|
|
normalizePlannedCrossForm,
|
|
updateCrossRow,
|
|
updatePlannedCrossRow,
|
|
} from "../api";
|
|
import { useCrossPedigree } from "../CrossPedigreeContext";
|
|
import { NONE_SELECT_VALUE } from "../types";
|
|
|
|
export function CrossEntityTab() {
|
|
const { snapshot, refresh } = useCrossPedigree();
|
|
const [subTab, setSubTab] = useState("planned");
|
|
|
|
const crossingProjectOptions = snapshot?.crossingProjectOptions ?? [];
|
|
const plannedCrossOptions = snapshot?.plannedCrossOptions ?? [];
|
|
|
|
const loadPlannedRows = useCallback(async () => {
|
|
const data = await refresh(false);
|
|
return data.plannedCrosses as unknown as Record<string, unknown>[];
|
|
}, [refresh]);
|
|
|
|
const loadActualRows = useCallback(async () => {
|
|
const data = await refresh(false);
|
|
return data.actualCrosses as unknown as Record<string, unknown>[];
|
|
}, [refresh]);
|
|
|
|
const fetchPlannedRecord = useCallback(async (id: string) => {
|
|
const data = await refresh(false);
|
|
const row = data.plannedCrosses.find((item) => item.id === id);
|
|
if (!row) throw new Error("计划杂交不存在");
|
|
return normalizePlannedCrossForm(row);
|
|
}, [refresh]);
|
|
|
|
const fetchActualRecord = useCallback(async (id: string) => {
|
|
const data = await refresh(false);
|
|
const row = data.actualCrosses.find((item) => item.id === id);
|
|
if (!row) throw new Error("实际杂交不存在");
|
|
return normalizeCrossForm(row);
|
|
}, [refresh]);
|
|
|
|
const plannedFields = useMemo<BrapiFormField[]>(() => [
|
|
{
|
|
key: "name",
|
|
label: "计划杂交名称",
|
|
type: "text",
|
|
required: true,
|
|
placeholder: "如 B73 x Mo17 / Cross-2026-001",
|
|
},
|
|
{
|
|
key: "crossing_project_id",
|
|
label: "杂交项目",
|
|
type: "select",
|
|
required: true,
|
|
options: [{ value: NONE_SELECT_VALUE, label: "请选择杂交项目" }, ...crossingProjectOptions],
|
|
},
|
|
{
|
|
key: "cross_type",
|
|
label: "杂交类型",
|
|
type: "select",
|
|
options: [{ value: NONE_SELECT_VALUE, label: "不指定类型" }, ...CROSS_TYPE_OPTIONS],
|
|
},
|
|
{
|
|
key: "status",
|
|
label: "状态",
|
|
type: "select",
|
|
options: PLANNED_CROSS_STATUS_OPTIONS,
|
|
},
|
|
], [crossingProjectOptions]);
|
|
|
|
const actualFields = useMemo<BrapiFormField[]>(() => [
|
|
{
|
|
key: "name",
|
|
label: "实际杂交名称",
|
|
type: "text",
|
|
required: true,
|
|
placeholder: "如 B73 x Mo17 实际杂交",
|
|
},
|
|
{
|
|
key: "crossing_project_id",
|
|
label: "杂交项目",
|
|
type: "select",
|
|
required: true,
|
|
options: [{ value: NONE_SELECT_VALUE, label: "请选择杂交项目" }, ...crossingProjectOptions],
|
|
},
|
|
{
|
|
key: "cross_type",
|
|
label: "杂交类型",
|
|
type: "select",
|
|
options: [{ value: NONE_SELECT_VALUE, label: "不指定类型" }, ...CROSS_TYPE_OPTIONS],
|
|
},
|
|
{
|
|
key: "planned_cross_id",
|
|
label: "来源计划杂交",
|
|
type: "select",
|
|
options: [{ value: NONE_SELECT_VALUE, label: "不关联计划杂交" }, ...plannedCrossOptions],
|
|
},
|
|
], [crossingProjectOptions, plannedCrossOptions]);
|
|
|
|
return (
|
|
<Tabs value={subTab} onValueChange={setSubTab} className="flex min-h-full flex-col gap-4">
|
|
<TabsList className="w-full justify-start overflow-x-auto rounded-lg border bg-white p-1 dark:border-slate-800 dark:bg-slate-950 sm:w-fit">
|
|
<TabsTrigger value="planned">计划杂交 (planned=true)</TabsTrigger>
|
|
<TabsTrigger value="actual">实际杂交 (planned=false)</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{subTab === "planned" ? (
|
|
<TabsContent value="planned" className="mt-0 min-h-0 flex-1">
|
|
<BrapiEntityPage
|
|
icon={GitFork}
|
|
iconBg="bg-gradient-to-br from-emerald-500 to-green-600"
|
|
title="计划杂交"
|
|
description="cross_entity(planned=true):录入杂交计划;亲本与详情页 Parents Tab 维护"
|
|
addLabel="新增计划杂交"
|
|
useEnhancedDialog
|
|
columns={[
|
|
{
|
|
key: "plannedCrossDbId",
|
|
label: "Cross ID",
|
|
render: (value, row) => {
|
|
const id = String(row.id ?? row.plannedCrossDbId ?? value ?? "");
|
|
if (!id) return "—";
|
|
return (
|
|
<Link
|
|
href={`/germplasm/cross-pedigree/planned-crosses/${encodeURIComponent(id)}`}
|
|
className="font-medium text-emerald-600 hover:underline dark:text-emerald-400"
|
|
>
|
|
{id}
|
|
</Link>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
key: "name",
|
|
label: "名称",
|
|
render: (value, row) => {
|
|
const id = String(row.id ?? row.plannedCrossDbId ?? "");
|
|
const name = String(value ?? "—");
|
|
if (!id) return name;
|
|
return (
|
|
<Link
|
|
href={`/germplasm/cross-pedigree/planned-crosses/${encodeURIComponent(id)}`}
|
|
className="font-medium text-emerald-600 hover:underline dark:text-emerald-400"
|
|
>
|
|
{name}
|
|
</Link>
|
|
);
|
|
},
|
|
},
|
|
{ key: "crossing_project_name", label: "杂交项目" },
|
|
{ key: "cross_type", label: "类型", render: crossTypeLabel },
|
|
{ key: "status", label: "状态", render: plannedStatusLabel },
|
|
]}
|
|
fields={plannedFields}
|
|
data={[]}
|
|
stats={[
|
|
{
|
|
label: "/brapi/v2/plannedcrosses",
|
|
value: "BrAPI",
|
|
className: "bg-emerald-50 text-emerald-700 dark:bg-emerald-400/10 dark:text-emerald-200",
|
|
},
|
|
]}
|
|
loadData={loadPlannedRows}
|
|
fetchRecord={fetchPlannedRecord}
|
|
createRecord={(payload) => createPlannedCrossRow(payload) as unknown as Promise<Record<string, unknown>>}
|
|
updateRecord={(id, payload) => updatePlannedCrossRow(id, payload) as unknown as Promise<Record<string, unknown>>}
|
|
/>
|
|
</TabsContent>
|
|
) : null}
|
|
|
|
{subTab === "actual" ? (
|
|
<TabsContent value="actual" className="mt-0 min-h-0 flex-1">
|
|
<BrapiEntityPage
|
|
icon={GitFork}
|
|
iconBg="bg-gradient-to-br from-green-600 to-emerald-700"
|
|
title="实际杂交"
|
|
description="cross_entity(planned=false):完成实际杂交后可关联来源计划杂交并继承亲本;详情页维护 Parents 与授粉事件"
|
|
addLabel="新增实际杂交"
|
|
useEnhancedDialog
|
|
columns={[
|
|
{
|
|
key: "crossDbId",
|
|
label: "Cross ID",
|
|
render: (value, row) => {
|
|
const id = String(row.id ?? row.crossDbId ?? value ?? "");
|
|
if (!id) return "—";
|
|
return (
|
|
<Link
|
|
href={`/germplasm/cross-pedigree/crosses/${encodeURIComponent(id)}`}
|
|
className="font-medium text-green-600 hover:underline dark:text-green-400"
|
|
>
|
|
{id}
|
|
</Link>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
key: "name",
|
|
label: "名称",
|
|
render: (value, row) => {
|
|
const id = String(row.id ?? row.crossDbId ?? "");
|
|
const name = String(value ?? "—");
|
|
if (!id) return name;
|
|
return (
|
|
<Link
|
|
href={`/germplasm/cross-pedigree/crosses/${encodeURIComponent(id)}`}
|
|
className="font-medium text-green-600 hover:underline dark:text-green-400"
|
|
>
|
|
{name}
|
|
</Link>
|
|
);
|
|
},
|
|
},
|
|
{ key: "crossing_project_name", label: "杂交项目" },
|
|
{ key: "plannedCrossName", label: "来源计划杂交" },
|
|
{ key: "cross_type", label: "类型", render: crossTypeLabel },
|
|
]}
|
|
fields={actualFields}
|
|
data={[]}
|
|
stats={[
|
|
{
|
|
label: "/brapi/v2/crosses",
|
|
value: "BrAPI",
|
|
className: "bg-green-50 text-green-700 dark:bg-green-400/10 dark:text-green-200",
|
|
},
|
|
]}
|
|
loadData={loadActualRows}
|
|
fetchRecord={fetchActualRecord}
|
|
createRecord={(payload) => createCrossRow(payload) as unknown as Promise<Record<string, unknown>>}
|
|
updateRecord={(id, payload) => updateCrossRow(id, payload) as unknown as Promise<Record<string, unknown>>}
|
|
/>
|
|
</TabsContent>
|
|
) : null}
|
|
</Tabs>
|
|
);
|
|
}
|