(key: K, value: CreateOpportunityFollowUpPayload[K]) => {
setFollowUpForm((current) => ({ ...current, [key]: value }));
};
const handleOpenCreate = () => {
setError("");
setForm(defaultForm);
setCreateOpen(true);
};
const resetCreateState = () => {
setCreateOpen(false);
setEditOpen(false);
setSubmitting(false);
setError("");
setForm(defaultForm);
};
const resetFollowUpState = () => {
setFollowUpOpen(false);
setFollowUpError("");
setFollowUpForm({
...defaultFollowUpForm,
followUpTime: toDateTimeLocalValue(),
});
};
const reload = async (preferredSelectedId?: number) => {
const data = await getOpportunityOverview(keyword, filter);
const nextItems = data.items ?? [];
setItems(nextItems);
if (preferredSelectedId) {
setSelectedItem(nextItems.find((item) => item.id === preferredSelectedId) ?? null);
}
};
const handleCreateSubmit = async () => {
if (submitting) {
return;
}
setSubmitting(true);
setError("");
try {
await createOpportunity(form);
await reload();
resetCreateState();
} catch (createError) {
setError(createError instanceof Error ? createError.message : "新增商机失败");
setSubmitting(false);
}
};
const handleOpenEdit = () => {
if (!selectedItem) {
return;
}
setError("");
setForm(toFormFromItem(selectedItem));
setEditOpen(true);
};
const handleEditSubmit = async () => {
if (!selectedItem || submitting) {
return;
}
setSubmitting(true);
setError("");
try {
await updateOpportunity(selectedItem.id, form);
await reload(selectedItem.id);
resetCreateState();
} catch (updateError) {
setError(updateError instanceof Error ? updateError.message : "编辑商机失败");
setSubmitting(false);
}
};
const handleOpenFollowUp = () => {
if (!selectedItem) {
return;
}
setFollowUpError("");
setFollowUpForm({
followUpType: "电话沟通",
content: "",
nextAction: "",
followUpTime: toDateTimeLocalValue(),
});
setFollowUpOpen(true);
};
const handleFollowUpSubmit = async () => {
if (!selectedItem || submitting) {
return;
}
setSubmitting(true);
setFollowUpError("");
try {
await createOpportunityFollowUp(selectedItem.id, {
...followUpForm,
nextAction: followUpForm.nextAction || undefined,
followUpTime: new Date(followUpForm.followUpTime).toISOString(),
});
await reload();
resetFollowUpState();
setSelectedItem(null);
setSubmitting(false);
} catch (submitError) {
setFollowUpError(submitError instanceof Error ? submitError.message : "新增跟进失败");
setSubmitting(false);
}
};
const renderEmpty = () => (
暂无商机数据,先新增一条试试。
);
return (
setKeyword(event.target.value)}
className="w-full rounded-xl border border-slate-200 bg-white py-2.5 pl-10 pr-4 text-sm text-slate-900 outline-none transition-all placeholder:text-slate-400 focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50 dark:text-white dark:placeholder:text-slate-500"
/>
{stageOptions.map((stage) => (
))}
{items.length > 0 ? (
items.map((opp, i) => (
setSelectedItem(opp)}
className="group relative cursor-pointer rounded-2xl border border-slate-100 bg-white p-5 shadow-sm backdrop-blur-sm transition-all hover:border-violet-100 hover:shadow-md dark:border-slate-800 dark:bg-slate-900/50 dark:hover:border-violet-900/50"
>
{opp.code || `#${opp.id}`}
{opp.pushedToOms ? (
已推OMS
) : null}
{opp.name || "未命名商机"}
{opp.confidence ?? 0}%
把握度
{opp.client || "未命名客户"}
¥{formatAmount(opp.amount)}
{opp.date || "未设置"}
类型:
{opp.type || "新建"}
))
) : renderEmpty()}
{(createOpen || editOpen) && (
)}
>
{error ? {error}
: null}
)}
{followUpOpen && selectedItem && (
)}
>
{followUpError ? {followUpError}
: null}
)}
{selectedItem && (
<>
setSelectedItem(null)}
className={`fixed inset-0 z-40 bg-slate-900/20 backdrop-blur-sm transition-opacity dark:bg-slate-900/60 ${hasForegroundModal ? "pointer-events-none opacity-30" : ""}`}
/>
{selectedItem.code || `#${selectedItem.id}`}
{selectedItem.pushedToOms ? 已推OMS : null}
{selectedItem.name || "未命名商机"}
{selectedItem.stage || "初步沟通"}
把握度 {selectedItem.confidence ?? 0}%
基本信息
客户名称
{selectedItem.client || "无"}
商机金额
¥{formatAmount(selectedItem.amount)}
预计结单
{selectedItem.date || "无"}
负责人
{selectedItem.owner || "当前用户"}
商机类型
{selectedItem.type || "新建"}
产品类别
{selectedItem.product || "无"}
商机来源
{selectedItem.source || "无"}
备注说明
{selectedItem.notes || "无"}
跟进记录
{followUpRecords.length > 0 ? (
{followUpRecords.map((record) => (
{record.type || "无"}
{record.date || "无"}
{record.content || "无"}
跟进人: {record.user || "无"}
))}
) : (
暂无跟进记录
)}
>
)}
);
}