# NOTE系統日期小幫手

[Claude](https://claude.site/artifacts/1be04710-ba3d-4ce2-ac41-bd0765cd5be9)
```javascript
import React, { useState, useRef, KeyboardEvent } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Clock, Calendar, CalendarPlus } from "lucide-react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
type DateFormat = {
id: string;
label: string;
format: (date: Date) => string;
};
const dateFormats: DateFormat[] = [
{
id: 'slash',
label: 'Y/M/D',
format: (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}/${month}/${day}`;
}
},
{
id: 'dash',
label: 'Y-M-D',
format: (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
},
{
id: 'long',
label: 'M. D, Y',
format: (date: Date) => {
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const year = date.getFullYear();
const month = months[date.getMonth()];
const day = String(date.getDate()).padStart(2, '0');
return `${month}. ${day}, ${year}`;
}
}
];
const RichTextEditor = () => {
const [value, setValue] = useState('');
const [wordCount, setWordCount] = useState(0);
const [charCount, setCharCount] = useState(0);
const [dateFormat, setDateFormat] = useState<string>('slash');
const textareaRef = useRef<HTMLTextAreaElement>(null);
const updateCounts = (text: string) => {
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
setWordCount(words);
setCharCount(text.length);
};
const getCurrentFormat = () => {
return dateFormats.find(format => format.id === dateFormat) || dateFormats[0];
};
const calculateDateTime = (match: string) => {
const regex = /\+(\d*\.?\d+)([hdmw])/;
const matches = match.match(regex);
if (!matches) return null;
const amount = parseFloat(matches[1]);
const unit = matches[2];
const date = new Date();
switch (unit) {
case 'h':
const totalMinutes = amount * 60;
date.setMinutes(date.getMinutes() + totalMinutes);
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
case 'd':
date.setDate(date.getDate() + amount);
break;
case 'm':
date.setDate(date.getDate() + (amount * 30));
break;
case 'w':
date.setDate(date.getDate() + (amount * 7));
break;
default:
return null;
}
return getCurrentFormat().format(date);
};
const handleRenderDate = () => {
const text = value;
const regex = /\+\d*\.?\d+[hdmw]/g;
const matches = text.match(regex);
if (!matches) {
alert("找不到有效的日期或時間格式!\n請使用以下格式:\n- +7d (7天後)\n- +7w (7週後)\n- +7m (7個月後)\n- +7h (7小時後)\n- +0.5h (30分鐘後)");
return;
}
let newText = text;
matches.forEach(match => {
const calculated = calculateDateTime(match);
if (calculated) {
newText = newText.replace(match, calculated);
}
});
setValue(newText);
updateCounts(newText);
};
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
if (e.ctrlKey && e.key === 'd') {
e.preventDefault();
const date = new Date();
const dateStr = getCurrentFormat().format(date);
const textarea = textareaRef.current;
if (textarea) {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const newValue = value.substring(0, start) + dateStr + value.substring(end);
setValue(newValue);
updateCounts(newValue);
setTimeout(() => {
textarea.selectionStart = textarea.selectionEnd = start + dateStr.length;
}, 0);
}
}
if (e.ctrlKey && e.key === 't') {
e.preventDefault();
const date = new Date();
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const timeStr = `${hours}:${minutes}`;
const textarea = textareaRef.current;
if (textarea) {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const newValue = value.substring(0, start) + timeStr + value.substring(end);
setValue(newValue);
updateCounts(newValue);
setTimeout(() => {
textarea.selectionStart = textarea.selectionEnd = start + timeStr.length;
}, 0);
}
}
};
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newValue = e.target.value;
setValue(newValue);
updateCounts(newValue);
};
return (
<Card className="w-full max-w-3xl">
<CardHeader className="space-y-1">
<div className="flex justify-between items-center flex-nowrap min-w-0">
<CardTitle className="text-2xl whitespace-nowrap overflow-hidden text-ellipsis">文字編輯器</CardTitle>
<Select value={dateFormat} onValueChange={setDateFormat}>
<SelectTrigger className="w-[100px] ml-4 flex-shrink-0">
<SelectValue placeholder="日期格式" />
</SelectTrigger>
<SelectContent>
{dateFormats.map(format => (
<SelectItem key={format.id} value={format.id}>
{format.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex flex-wrap gap-4 text-sm text-gray-500">
<div className="flex items-center space-x-1">
<Calendar className="h-4 w-4" />
<span>按Ctrl+D插入日期</span>
</div>
<div className="flex items-center space-x-1">
<Clock className="h-4 w-4" />
<span>按Ctrl+T插入時間</span>
</div>
<div className="flex items-center space-x-1">
<CalendarPlus className="h-4 w-4" />
<span>使用 +7d, +7w 或 +7m 計算未來日期</span>
</div>
<div className="flex items-center space-x-1">
<Clock className="h-4 w-4" />
<span>使用 +7h 或 +0.5h 計算未來時間</span>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<textarea
ref={textareaRef}
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
className="w-full h-96 p-4 rounded-lg border border-gray-200 focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none font-mono text-base leading-relaxed outline-none"
placeholder="開始輸入... 使用 +7d 代表7天後,+7w 代表7週後,+7m 代表7個月後,+7h 代表7小時後,+0.5h 代表30分鐘後"
spellCheck="false"
/>
<div className="flex justify-between items-center">
<div className="text-sm text-gray-500">
{wordCount} {wordCount === 1 ? '個字' : '個字'} | {charCount} {charCount === 1 ? '個字元' : '個字元'}
</div>
<Button
onClick={handleRenderDate}
className="flex items-center gap-2"
>
<CalendarPlus className="h-4 w-4" />
計算日期時間
</Button>
</div>
</CardContent>
</Card>
);
};
export default RichTextEditor;
```