215 lines
7.4 KiB
TypeScript
215 lines
7.4 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import {
|
||
BookOpenText,
|
||
MessageSquareText,
|
||
Send,
|
||
Users,
|
||
} from "lucide-react";
|
||
|
||
import { RawDetails } from "@/components/display";
|
||
import {
|
||
EmptyState,
|
||
ErrorBanner,
|
||
Loading,
|
||
PageHeading,
|
||
} from "@/components/feedback";
|
||
import { Badge } from "@/components/ui/badge";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||
import { Input } from "@/components/ui/input";
|
||
import { Label } from "@/components/ui/label";
|
||
import { Select } from "@/components/ui/select";
|
||
import { Textarea } from "@/components/ui/textarea";
|
||
import { UsageGuide } from "@/components/usage-guide";
|
||
import { ApiError } from "@/lib/api";
|
||
import { academicApi } from "@/lib/services";
|
||
|
||
const TYPES = [
|
||
{ value: "journal-club", label: "文献汇报" },
|
||
{ value: "case-discussion", label: "病例讨论" },
|
||
{ value: "seminar", label: "专题讲座" },
|
||
{ value: "rounds", label: "教学查房" },
|
||
];
|
||
|
||
export default function AcademicPage() {
|
||
const [records, setRecords] = useState<any[]>([]);
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
const [type, setType] = useState("");
|
||
const [title, setTitle] = useState("");
|
||
const [content, setContent] = useState("");
|
||
const [mentorFeedback, setMentorFeedback] = useState("");
|
||
const [submitting, setSubmitting] = useState(false);
|
||
|
||
async function loadRecords() {
|
||
setLoading(true);
|
||
setError(null);
|
||
try {
|
||
const res = await academicApi.listRecords();
|
||
setRecords(Array.isArray(res) ? res : []);
|
||
} catch (err) {
|
||
setError(err instanceof ApiError ? err.message : "加载失败");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
|
||
async function handleSubmit(e: React.FormEvent) {
|
||
e.preventDefault();
|
||
setSubmitting(true);
|
||
setError(null);
|
||
try {
|
||
await academicApi.addRecord({
|
||
type: type || undefined,
|
||
title,
|
||
content,
|
||
mentorFeedback: mentorFeedback || undefined,
|
||
});
|
||
setType("");
|
||
setTitle("");
|
||
setContent("");
|
||
setMentorFeedback("");
|
||
await loadRecords();
|
||
} catch (err) {
|
||
setError(err instanceof ApiError ? err.message : "提交失败");
|
||
} finally {
|
||
setSubmitting(false);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<PageHeading
|
||
icon={<Users className="size-5" />}
|
||
title="学术交流"
|
||
description="文献汇报、病例讨论记录与导师反馈,沉淀学术交流轨迹。"
|
||
/>
|
||
|
||
<UsageGuide
|
||
steps={[
|
||
{ title: "选择交流类型", detail: "文献汇报、病例讨论、专题讲座或教学查房。" },
|
||
{ title: "填写主题与内容", detail: "记录汇报主题、核心观点与个人收获。" },
|
||
{ title: "记录导师反馈", detail: "将导师的点评与建议同步记录,便于后续回顾。" },
|
||
{ title: "查看交流轨迹", detail: "按时间线查看所有学术交流记录与成长脉络。" },
|
||
]}
|
||
tip="高质量的学术交流记录是胜任力画像中「学术与科研」维度的重要数据来源,建议每次活动后 24 小时内完成记录。"
|
||
/>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Send className="size-4" />
|
||
新增交流记录
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<form onSubmit={handleSubmit} className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||
<div>
|
||
<Label>交流类型</Label>
|
||
<Select value={type} onChange={(e) => setType(e.target.value)} required>
|
||
<option value="">请选择</option>
|
||
{TYPES.map((t) => (
|
||
<option key={t.value} value={t.value}>
|
||
{t.label}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
</div>
|
||
<div>
|
||
<Label>主题</Label>
|
||
<Input
|
||
value={title}
|
||
onChange={(e) => setTitle(e.target.value)}
|
||
placeholder="如:糖尿病肾病最新诊疗进展"
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="sm:col-span-2">
|
||
<Label>内容摘要</Label>
|
||
<Textarea
|
||
value={content}
|
||
onChange={(e) => setContent(e.target.value)}
|
||
placeholder="记录汇报内容、核心观点、个人收获…"
|
||
rows={4}
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="sm:col-span-2">
|
||
<Label>导师反馈(可选)</Label>
|
||
<Textarea
|
||
value={mentorFeedback}
|
||
onChange={(e) => setMentorFeedback(e.target.value)}
|
||
placeholder="导师点评、建议与改进方向…"
|
||
rows={3}
|
||
/>
|
||
</div>
|
||
<div className="flex items-center gap-3 sm:col-span-2">
|
||
<Button type="submit" disabled={submitting}>
|
||
{submitting ? "提交中…" : "提交记录"}
|
||
</Button>
|
||
<Button type="button" variant="outline" onClick={loadRecords} disabled={loading}>
|
||
{loading ? "加载中…" : "刷新记录"}
|
||
</Button>
|
||
</div>
|
||
{error && (
|
||
<div className="sm:col-span-2">
|
||
<ErrorBanner message={error} />
|
||
</div>
|
||
)}
|
||
</form>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<BookOpenText className="size-4" />
|
||
交流记录
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
{loading ? (
|
||
<Loading />
|
||
) : records.length === 0 ? (
|
||
<EmptyState message="暂无学术交流记录,请添加第一条记录。" />
|
||
) : (
|
||
<div className="space-y-4">
|
||
{records.map((r: any, i: number) => (
|
||
<div
|
||
key={r.id ?? i}
|
||
className="rounded-lg border border-border p-4"
|
||
>
|
||
<div className="mb-2 flex items-center justify-between">
|
||
<div className="flex items-center gap-2">
|
||
<Badge variant="outline">
|
||
{TYPES.find((t) => t.value === r.type)?.label ?? r.type}
|
||
</Badge>
|
||
<span className="text-sm font-semibold text-foreground">{r.title}</span>
|
||
</div>
|
||
{r.date && (
|
||
<span className="text-xs text-muted-foreground">{r.date}</span>
|
||
)}
|
||
</div>
|
||
<p className="text-sm leading-relaxed text-foreground/80">{r.content}</p>
|
||
{r.mentorFeedback && (
|
||
<div className="mt-3 rounded-lg bg-muted/50 p-3">
|
||
<p className="mb-1 flex items-center gap-1 text-xs font-medium text-muted-foreground">
|
||
<MessageSquareText className="size-3" />
|
||
导师反馈
|
||
</p>
|
||
<p className="text-sm text-foreground/80">{r.mentorFeedback}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|