256 lines
13 KiB
TypeScript
256 lines
13 KiB
TypeScript
|
|
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>
|
|||
|
|
);
|
|||
|
|
}
|