guanghu-research/src/pages/ReceiptPage.tsx

87 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>;
}