thread-master/apps/web/src/components/radar/OpportunityDetailDrawer.tsx

54 lines
3.2 KiB
TypeScript
Raw Normal View History

2026-08-13 02:22:24 +00:00
import type { Opportunity } from "../../domain/types";
import { Badge, Button } from "../ui";
import { ProductMatchDetails } from "./ProductMatchDetails";
type Props = {
opportunity: Opportunity;
onClose: () => void;
onAccept?: (opportunity: Opportunity) => void;
onComplete?: (opportunity: Opportunity) => void;
busy?: boolean;
};
export function OpportunityDetailDrawer({ opportunity, onClose, onAccept, onComplete, busy = false }: Props) {
const pending = (opportunity.review_state || "pending") === "pending";
const accepted = opportunity.status === "accepted" || Boolean(opportunity.contact_id);
return (
<aside className="hb-opp-drawer" role="dialog" aria-modal="true" aria-label="商機詳情">
<div className="hb-opp-drawer__head">
<div>
<span className="hb-radar-section__hint"></span>
<h2>{opportunity.primary_product_label || "未指定產品"}</h2>
</div>
<Button type="button" variant="ghost" aria-label="關閉商機詳情" onClick={onClose}></Button>
</div>
<div className="hb-opp-drawer__body">
<p className="hb-opp-card__text">{opportunity.text}</p>
<div className="hb-opp-card__meta">
<Badge tone="brand"> {opportunity.intent_score}</Badge>
{opportunity.priority_score !== undefined ? <Badge tone="warning"> {opportunity.priority_score}</Badge> : null}
<span>@{opportunity.author_handle || "未知作者"}</span>
</div>
{opportunity.demand_evidence?.length ? (
<section className="hb-opp-drawer__section"><h3></h3><ul>{opportunity.demand_evidence.map((item) => <li key={item}>{item}</li>)}</ul></section>
) : null}
<section className="hb-opp-drawer__section">
<h3></h3>
{opportunity.product_matches?.length ? opportunity.product_matches.map((match) => <ProductMatchDetails key={match.product_id} match={match} />) : <p className="hb-radar-section__hint"></p>}
</section>
{opportunity.reasons.length ? <section className="hb-opp-drawer__section"><h3></h3><div className="hb-opp-reasons">{opportunity.reasons.map((reason) => <div className="hb-opp-reason" key={reason.dimension}><strong>{reason.dimension}</strong><span>{reason.score}</span><span>{reason.reason}</span></div>)}</div></section> : null}
<div className="hb-opp-drawer__actions">
{pending && onComplete ? (
2026-08-13 07:54:25 +00:00
<Button type="button" disabled={busy} onClick={() => onComplete(opportunity)}></Button>
) : null}
{pending && !accepted && onAccept ? (
<Button type="button" variant="ghost" disabled={busy} onClick={() => onAccept(opportunity)}></Button>
2026-08-13 02:22:24 +00:00
) : null}
<a className="hb-btn hb-btn--ghost" href={opportunity.permalink} target="_blank" rel="noreferrer"> Threads </a>
</div>
2026-08-13 07:54:25 +00:00
{pending ? <p className="hb-field__hint"></p> : null}
2026-08-13 02:22:24 +00:00
</div>
</aside>
);
}