unis_crm/frontend/src/pages/Work.tsx

256 lines
13 KiB
TypeScript
Raw Normal View History

2026-03-19 06:23:03 +00:00
import { useState } from "react";
import { MapPin, Camera, Mic, Send, CalendarDays, Clock, CheckCircle2, FileText, ListTodo, Search, Filter, History } from "lucide-react";
import { format } from "date-fns";
import { motion } from "motion/react";
export default function Work() {
const [isRecording, setIsRecording] = useState(false);
const [reportText, setReportText] = useState("");
const handleRecord = () => {
setIsRecording(!isRecording);
if (!isRecording) {
// Mock voice to text
setTimeout(() => {
setReportText((prev) => prev + (prev ? "\n" : "") + "今天拜访了A市第一人民医院信息科主任沟通了云桌面扩容需求对方表示下个月会启动招标流程。");
setIsRecording(false);
}, 2000);
}
};
const historyData = [
{
id: 1,
date: "2024-03-15",
type: "日报",
content: "1. 拜访了A市第一人民医院信息科主任沟通了云桌面扩容需求...\n2. 与B大学新校区机房建设项目的负责人进行了电话沟通...",
status: "已点评",
score: 95,
comment: "跟进很紧密继续保持。A市医院的项目需要重点关注招标时间点。"
},
{
id: 2,
date: "2024-03-14",
type: "外勤打卡",
content: "浙江省杭州市西湖区某某街道某某大厦 (C集团总部)",
status: "正常",
score: null,
comment: null
},
{
id: 3,
date: "2024-03-14",
type: "日报",
content: "1. 整理了C集团办公云桌面替换项目的POC测试报告...\n2. 参加了华东大区周例会...",
status: "已阅",
score: null,
comment: null
},
{
id: 4,
date: "2024-03-13",
type: "外勤打卡",
content: "上海市浦东新区某某路某某科技园 (D公司)",
status: "正常",
score: null,
comment: null
}
];
return (
<div className="space-y-6">
<header className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold tracking-tight text-slate-900 dark:text-white"></h1>
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1">
{format(new Date(), "yyyy年MM月dd日 EEEE")}
</p>
</div>
</header>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 items-start">
{/* Left Column: Today's Actionable Items */}
<div className="lg:col-span-7 xl:col-span-8 space-y-6">
<div className="flex items-center gap-2 mb-2">
<div className="h-6 w-1 bg-violet-600 rounded-full"></div>
<h2 className="text-lg font-semibold text-slate-900 dark:text-white"></h2>
</div>
{/* Check-in Card */}
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="rounded-2xl border border-slate-100 dark:border-slate-800 bg-white dark:bg-slate-900/50 p-6 shadow-sm backdrop-blur-sm"
>
<div className="flex items-center justify-between border-b border-slate-50 dark:border-slate-800/50 pb-4 mb-4">
<div className="flex items-center gap-2">
<MapPin className="h-5 w-5 text-emerald-500 dark:text-emerald-400" />
<h3 className="text-base font-semibold text-slate-900 dark:text-white"></h3>
</div>
<span className="text-xs font-medium text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-500/10 px-2.5 py-1 rounded-full">
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div>
<p className="text-sm font-medium text-slate-900 dark:text-white mb-1"></p>
<p className="text-sm text-slate-600 dark:text-slate-300 bg-slate-50 dark:bg-slate-800/50 p-3 rounded-xl border border-slate-100 dark:border-slate-800">
西
</p>
</div>
<div>
<p className="text-sm font-medium text-slate-900 dark:text-white mb-1"> ()</p>
<textarea
rows={2}
placeholder="请输入打卡备注..."
className="w-full rounded-xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900/50 p-3 text-sm text-slate-900 dark:text-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 transition-all placeholder:text-slate-400 dark:placeholder:text-slate-500"
/>
</div>
</div>
<div className="space-y-4 flex flex-col">
<p className="text-sm font-medium text-slate-900 dark:text-white mb-1"> ()</p>
<div className="group flex flex-1 min-h-[120px] w-full cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/50 transition-all hover:border-violet-400 dark:hover:border-violet-500 hover:bg-violet-50 dark:hover:bg-violet-500/10">
<Camera className="mb-2 h-6 w-6 text-slate-400 dark:text-slate-500 group-hover:text-violet-500 transition-colors" />
<span className="text-xs text-slate-500 dark:text-slate-400 group-hover:text-violet-600 dark:group-hover:text-violet-400 transition-colors"></span>
</div>
</div>
</div>
<button className="mt-6 flex w-full items-center justify-center gap-2 rounded-xl bg-slate-900 dark:bg-white px-4 py-3 text-sm font-semibold text-white dark:text-slate-900 shadow-sm hover:bg-slate-800 dark:hover:bg-slate-100 active:scale-[0.98] transition-all">
<CheckCircle2 className="h-4 w-4" />
</button>
</motion.div>
{/* Daily Report Card */}
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
className="rounded-2xl border border-slate-100 dark:border-slate-800 bg-white dark:bg-slate-900/50 p-6 shadow-sm backdrop-blur-sm"
>
<div className="flex items-center justify-between border-b border-slate-50 dark:border-slate-800/50 pb-4 mb-4">
<div className="flex items-center gap-2">
<CalendarDays className="h-5 w-5 text-violet-600 dark:text-violet-400" />
<h3 className="text-base font-semibold text-slate-900 dark:text-white"></h3>
</div>
<span className="text-xs font-medium text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-500/10 px-2.5 py-1 rounded-full">
</span>
</div>
<div className="space-y-5">
<div>
<div className="mb-2 flex items-center justify-between">
<label className="flex items-center gap-2 text-sm font-medium text-slate-900 dark:text-white">
<FileText className="h-4 w-4 text-slate-400 dark:text-slate-500" />
</label>
<button
onClick={handleRecord}
className={`flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium transition-all duration-300 ${
isRecording ? "bg-rose-100 dark:bg-rose-500/20 text-rose-600 dark:text-rose-400 animate-pulse" : "bg-violet-50 dark:bg-violet-500/10 text-violet-600 dark:text-violet-400 hover:bg-violet-100 dark:hover:bg-violet-500/20"
}`}
>
<Mic className="h-3.5 w-3.5" />
{isRecording ? "正在识别..." : "语音输入 (HubMind)"}
</button>
</div>
<textarea
rows={4}
value={reportText}
onChange={(e) => setReportText(e.target.value)}
placeholder="请输入今日拜访客户、沟通进展、遇到的问题等..."
className="w-full rounded-xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900/50 p-3 text-sm text-slate-900 dark:text-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 transition-all placeholder:text-slate-400 dark:placeholder:text-slate-500"
/>
</div>
<div>
<label className="mb-2 flex items-center gap-2 text-sm font-medium text-slate-900 dark:text-white">
<ListTodo className="h-4 w-4 text-slate-400 dark:text-slate-500" />
</label>
<textarea
rows={3}
placeholder="1. 上午拜访...\n2. 下午整理...\n3. 推进..."
className="w-full rounded-xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900/50 p-3 text-sm text-slate-900 dark:text-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 transition-all placeholder:text-slate-400 dark:placeholder:text-slate-500"
/>
</div>
<button className="flex w-full items-center justify-center gap-2 rounded-xl bg-violet-600 px-4 py-3 text-sm font-semibold text-white shadow-sm hover:bg-violet-700 active:scale-[0.98] transition-all">
<Send className="h-4 w-4" />
</button>
</div>
</motion.div>
</div>
{/* Right Column: History */}
<div className="lg:col-span-5 xl:col-span-4 space-y-6 lg:sticky lg:top-6">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<div className="h-6 w-1 bg-slate-300 dark:bg-slate-700 rounded-full"></div>
<h2 className="text-lg font-semibold text-slate-900 dark:text-white"></h2>
</div>
<button className="p-2 text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 transition-colors">
<Filter className="h-4 w-4" />
</button>
</div>
<div className="space-y-4 max-h-[calc(100vh-12rem)] overflow-y-auto pr-2 scrollbar-hide">
{historyData.map((item, i) => (
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: i * 0.1 }}
key={item.id}
className="group cursor-pointer rounded-2xl border border-slate-100 dark:border-slate-800 bg-white dark:bg-slate-900/50 p-4 shadow-sm backdrop-blur-sm transition-all hover:shadow-md hover:border-violet-100 dark:hover:border-violet-900/50"
>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<div className={`flex h-8 w-8 items-center justify-center rounded-full ${item.type === '日报' ? 'bg-blue-50 dark:bg-blue-500/10 text-blue-600 dark:text-blue-400' : 'bg-emerald-50 dark:bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'}`}>
{item.type === '日报' ? <FileText className="h-4 w-4" /> : <MapPin className="h-4 w-4" />}
</div>
<div>
<h3 className="text-sm font-semibold text-slate-900 dark:text-white">{item.type}</h3>
<p className="text-[10px] text-slate-500 dark:text-slate-400">{item.date}</p>
</div>
</div>
<div className="flex flex-col items-end gap-1">
<span className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${
item.status === '已点评' ? 'bg-violet-50 dark:bg-violet-500/10 text-violet-600 dark:text-violet-400' :
item.status === '已阅' ? 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400' :
'bg-emerald-50 dark:bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
}`}>
{item.status}
</span>
{item.score && (
<span className="text-[10px] font-bold text-rose-600 dark:text-rose-400">{item.score}</span>
)}
</div>
</div>
<div className="pl-11">
<p className="text-xs text-slate-700 dark:text-slate-300 line-clamp-2 leading-relaxed">
{item.content}
</p>
{item.comment && (
<div className="mt-2 rounded-lg bg-slate-50 dark:bg-slate-800/50 p-2.5 border border-slate-100 dark:border-slate-800/50">
<p className="text-[10px] font-medium text-slate-900 dark:text-white mb-0.5">:</p>
<p className="text-[10px] text-slate-600 dark:text-slate-400">{item.comment}</p>
</div>
)}
</div>
</motion.div>
))}
</div>
</div>
</div>
</div>
);
}