guanghu-research/src/pages/ReceiptPage.tsx

87 lines
4.3 KiB
TypeScript
Raw Normal View History

import React, { useEffect, useState } from "react";
import { fetchReceipts, ApiReceipt } from "../lib/api";
import { Page } from "../lib/types";
interface Props { onNavigate?: (page: Page) => void; }
export function ReceiptPage({ onNavigate }: Props) {
const [receipts, setReceipts] = useState<ApiReceipt[]>([]);
const [loading, setLoading] = useState(true);
const [expanded, setExpanded] = useState<string | null>(null);
const [search, setSearch] = useState("");
async function load() {
setLoading(true);
try { const data = await fetchReceipts(); setReceipts(data); } catch {}
setLoading(false);
}
useEffect(() => { load(); }, []);
const filtered = search
? receipts.filter(r => r.id.toLowerCase().includes(search.toLowerCase()) || r.operation.toLowerCase().includes(search.toLowerCase()))
: receipts;
const counts = { all: receipts.length, success: receipts.filter(r => r.result === "success").length, failed: receipts.filter(r => r.result !== "success").length };
return <div>
<h1 className="page-title">📝 回执记录</h1>
<div className="stats" style={{gridTemplateColumns:"repeat(3,1fr)"}}>
<div className="stat-card"><div className="stat-value">{counts.all}</div><div className="stat-label">全部</div></div>
<div className="stat-card"><div className="stat-value">{counts.success}</div><div className="stat-label">已成功</div></div>
<div className="stat-card"><div className="stat-value">{counts.failed}</div><div className="stat-label">待处理</div></div>
</div>
<div style={{marginBottom:"1rem"}}>
<input className="form-input" placeholder="🔍 搜索回执编号或描述..." value={search} onChange={e => setSearch(e.target.value)} />
</div>
{loading && <p style={{color:"#999"}}>加载中...</p>}
{!loading && filtered.length === 0 && <p style={{color:"#999"}}>{search ? "无匹配回执" : "暂无回执记录"}</p>}
{filtered.map(rc => {
const isExpanded = expanded === rc.id;
return <div className="card" key={rc.id} style={{cursor:"pointer"}} onClick={() => setExpanded(isExpanded ? null : rc.id)}>
<div className="card-header">
<div>
<div className="card-title">{rc.id}</div>
<div className="card-subtitle">{rc.operation}</div>
</div>
<span className={`badge ${rc.result==="success"?"badge-success":"badge-warning"}`}>
{rc.result==="success"?"✅ 已成功":"⚠️ 待验证"}
</span>
</div>
{isExpanded && <div style={{marginTop:"0.75rem",paddingTop:"0.75rem",borderTop:"1px solid var(--bd)"}}>
<table><tbody>
<tr><th style={{width:"100px"}}>执行者</th><td>{rc.operator}</td></tr>
<tr><th>完成时间</th><td>{rc.timestamp}</td></tr>
<tr><th>状态</th><td>{rc.result === "success" ? "成功闭环 ✅" : "需跟进 ⚠️"}</td></tr>
<tr><th>关联工单</th><td>
{onNavigate && <button className="btn" style={{fontSize:"0.75rem",padding:"0.2rem 0.5rem"}} onClick={e => { e.stopPropagation(); onNavigate("plan"); }}>
查看关联工单 →
</button>}
<span style={{fontSize:"0.75rem",color:"var(--t2)",marginLeft:"0.5rem"}}>{rc.id.replace("RC-","WO-")}</span>
</td></tr>
<tr><th>证据链</th><td>
<span className="badge badge-info" style={{fontSize:"0.7rem"}}>yeye 仓库 receipts/ 目录</span>
<span style={{fontSize:"0.75rem",color:"var(--t2)",marginLeft:"0.25rem"}}>Git 历史可追溯</span>
</td></tr>
</tbody></table>
<div style={{marginTop:"0.75rem",padding:"0.5rem",background:"var(--bg2)",borderRadius:"8px",fontSize:"0.8rem",color:"var(--t2)"}}>
<strong>HLDP 回执链</strong> · 工单 → 执行 → 结果 → 证据 · 不可静默覆盖
</div>
</div>}
<div style={{fontSize:"0.7rem",color:"var(--t2)",marginTop:"0.4rem"}}>
{isExpanded ? "▲ 收起" : "▼ 展开详情"} · {rc.timestamp?.split("T")[0] || "—"}
</div>
</div>;
})}
<div style={{marginTop:"1rem",fontSize:"0.75rem",color:"#999"}}>
数据源:guanghutansuohe.com/code/bingshuo/yeye/receipts/ · 最后同步 {new Date().toISOString().split("T")[0]}
</div>
</div>;
}