diff --git a/frontend/src/app/(investor)/reports/page.tsx b/frontend/src/app/(investor)/reports/page.tsx new file mode 100644 index 0000000..ab753fd --- /dev/null +++ b/frontend/src/app/(investor)/reports/page.tsx @@ -0,0 +1,157 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { FileText, Plus, Trash2, Send } from "lucide-react"; +import { listReports, submitReport, deleteReport, STATUS_LABELS, STATUS_COLORS, type MonthlyReport } from "@/lib/reports"; +import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; +import { EmptyState } from "@/components/shared/EmptyState"; + +/** + * 投资人端 — 月报管理页。 + */ +export default function ReportsPage() { + const [reports, setReports] = useState([]); + const [total, setTotal] = useState(0); + const [isLoading, setIsLoading] = useState(true); + const [page, setPage] = useState(1); + const pageSize = 20; + + const loadReports = useCallback(async () => { + setIsLoading(true); + try { + const resp = await listReports({ page, page_size: pageSize }); + if (resp.data) { + setReports(resp.data.items); + setTotal(resp.data.total); + } + } catch { + setReports([]); + } finally { + setIsLoading(false); + } + }, [page]); + + useEffect(() => { + loadReports(); + }, [loadReports]); + + async function handleSubmit(id: string) { + try { + await submitReport(id); + loadReports(); + } catch (err) { + alert(err instanceof Error ? err.message : "提交失败"); + } + } + + async function handleDelete(id: string) { + if (!confirm("确认删除该月报?")) return; + try { + await deleteReport(id); + loadReports(); + } catch (err) { + alert(err instanceof Error ? err.message : "删除失败"); + } + } + + return ( +
+
+
+

月报管理

+

共 {total} 份月报

+
+ +
+ + {isLoading ? ( +
+ +
+ ) : reports.length === 0 ? ( + + ) : ( +
+ + + + + + + + + + + + {reports.map((report) => ( + + + + + + + + ))} + +
期间状态内容摘要提交时间操作
+ {report.period_year}年{report.period_month}月 + + + {STATUS_LABELS[report.status] || report.status} + + + {report.raw_content || report.ai_summary || "暂无内容"} + + {report.submitted_at + ? new Date(report.submitted_at).toLocaleDateString("zh-CN") + : "—"} + +
+ {report.status === "draft" && ( + + )} + +
+
+
+ )} + + {total > pageSize && ( +
+ + + 第 {page} 页 / 共 {Math.ceil(total / pageSize)} 页 + + +
+ )} +
+ ); +} diff --git a/frontend/src/lib/reports.ts b/frontend/src/lib/reports.ts new file mode 100644 index 0000000..838cb37 --- /dev/null +++ b/frontend/src/lib/reports.ts @@ -0,0 +1,100 @@ +/** 月报相关类型和 API 函数。 */ + +import { apiFetch, type ApiResponse } from "./api"; + +/** 月报信息。 */ +export interface MonthlyReport { + id: string; + company_id: string; + period_year: number; + period_month: number; + status: string; + raw_content: string | null; + structured_data: Record | null; + ai_summary: string | null; + ai_concerns: Record | null; + submitted_by: string | null; + submitted_at: string | null; + reviewed_by: string | null; + reviewed_at: string | null; + created_at: string; + updated_at: string; +} + +/** 月报列表响应。 */ +export interface ReportListResponse { + items: MonthlyReport[]; + total: number; + page: number; + page_size: number; +} + +/** 创建月报参数。 */ +export interface ReportInput { + company_id: string; + period_year: number; + period_month: number; + raw_content?: string | null; +} + +/** 状态标签映射。 */ +export const STATUS_LABELS: Record = { + draft: "草稿", + submitted: "已提交", + ai_parsed: "AI 已解析", + reviewed: "已审阅", +}; + +/** 状态颜色映射。 */ +export const STATUS_COLORS: Record = { + draft: "bg-muted text-muted-foreground", + submitted: "bg-blue-100 text-blue-700", + ai_parsed: "bg-purple-100 text-purple-700", + reviewed: "bg-emerald-100 text-emerald-700", +}; + +/** + * 获取月报列表。 + */ +export async function listReports(params?: { + company_id?: string; + page?: number; + page_size?: number; +}): Promise> { + const query = new URLSearchParams(); + if (params?.company_id) query.set("company_id", params.company_id); + if (params?.page) query.set("page", String(params.page)); + if (params?.page_size) query.set("page_size", String(params.page_size)); + return apiFetch(`/reports?${query.toString()}`); +} + +/** + * 获取月报详情。 + */ +export async function getReport(id: string): Promise> { + return apiFetch(`/reports/${id}`); +} + +/** + * 创建月报。 + */ +export async function createReport(data: ReportInput): Promise> { + return apiFetch("/reports", { + method: "POST", + body: JSON.stringify(data), + }); +} + +/** + * 提交月报。 + */ +export async function submitReport(id: string): Promise> { + return apiFetch(`/reports/${id}/submit`, { method: "POST" }); +} + +/** + * 删除月报。 + */ +export async function deleteReport(id: string): Promise> { + return apiFetch(`/reports/${id}`, { method: "DELETE" }); +}