import React, { useState, useMemo } from 'react'; import { Calendar as CalendarIcon, Bike, Plus, X, Search, Filter, Settings, Check, User, Phone, MapPin, DollarSign, Eye, Truck, ChevronLeft, ChevronRight, LayoutList, Trash2 } from 'lucide-react'; --- 取得今日日期字串 (YYYY-MM-DD) --- const getTodayStr = () = { const now = new Date(); return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`; }; const todayStr = getTodayStr(); --- 初始模擬資料 --- const initialSchedules = [ { id 's1', type '約看', date todayStr, time '1400', vehicleModel 'YZF-R3', name '王先生', phone '0912-345-678', location '', amount '', notes '偏好藍色', status '待處理' }, { id 's2', type '收車', date todayStr, time '1630', vehicleModel 'CBR500R', name '林老闆', phone '0955-123-456', location '台南市南區大同路', amount '', notes '車況不錯,帶合約', status '已完成' }, 這一筆交車地點為「店內」,系統會自動將其排除於任何排程畫面之外 { id 's3', type '交車', date todayStr, time '1000', vehicleModel 'DRG BT 158', name '陳小姐', phone '0987-654-321', location '店內', amount '85000', notes '店裡交車不顯示', status '待處理' }, 這一筆為外縣市交車,會正常顯示 { id 's4', type '交車', date todayStr, time '1900', vehicleModel 'Ninja 400', name '張先生', phone '0988-111-222', location '高雄市左營區', amount '150000', notes '外送交車', status '待處理' } ]; export default function App() { const [schedules, setSchedules] = useState(initialSchedules); 視圖切換狀態: 'list' (列表) 或 'calendar' (日曆) const [viewMode, setViewMode] = useState('list'); --- 列表模式狀態 --- const [searchTerm, setSearchTerm] = useState(''); const [typeFilter, setTypeFilter] = useState('全部'); const [sortOrder, setSortOrder] = useState('newest'); --- 日曆模式狀態 --- const [currentMonth, setCurrentMonth] = useState(new Date(new Date().getFullYear(), new Date().getMonth(), 1)); const [selectedDate, setSelectedDate] = useState(todayStr); --- Modal 狀態 --- const [isFormModalOpen, setIsFormModalOpen] = useState(false); const [isStatusModalOpen, setIsStatusModalOpen] = useState(false); const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState(false); const [selectedItem, setSelectedItem] = useState(null); --- 表單狀態 --- const [formType, setFormType] = useState('約看'); const initialForm = useMemo(() = ({ date todayStr, time '1200', vehicleModel '', name '', phone '', location '', amount '', notes '' }), []); const [formData, setFormData] = useState(initialForm); const [statusUpdateData, setStatusUpdateData] = useState({ status '', remarks '' }); ========================================== 🚀 核心過濾邏輯:全局排除店內交車 ========================================== const validSchedules = useMemo(() = { return schedules.filter(item = { if (item.type === '交車') { const loc = (item.location '').trim(); const isInsideShop = !loc ['店裡', '店內', '本店', '公司'].some(keyword = loc.includes(keyword)); if (isInsideShop) return false; } return true; }); }, [schedules]); --- 列表模式:資料篩選與排序 --- const filteredSchedules = useMemo(() = { return validSchedules.filter(item = { const matchSearch = ( (item.name '') + (item.phone '') + (item.vehicleModel '') + (item.location '') ).toLowerCase().includes(searchTerm.toLowerCase()); const matchType = typeFilter === '全部' item.type === typeFilter; return matchSearch && matchType; }).sort((a, b) = { const dateA = new Date(`${a.date}T${a.time}`).getTime(); const dateB = new Date(`${b.date}T${b.time}`).getTime(); return sortOrder === 'newest' dateB - dateA dateA - dateB; }); }, [validSchedules, searchTerm, typeFilter, sortOrder]); --- 日曆模式:取得選取日期的行程 --- const selectedDayEvents = useMemo(() = { return validSchedules .filter(s = s.date === selectedDate) .sort((a, b) = a.time.localeCompare(b.time)); }, [validSchedules, selectedDate]); --- 統計數據 (列表用:全局今日) --- const listStats = useMemo(() = { const todayItems = validSchedules.filter(s = s.date === todayStr); return { todayCount todayItems.length, viewCount validSchedules.filter(s = s.type === '約看' && s.status !== '已取消').length, buyCount validSchedules.filter(s = s.type === '收車' && s.status !== '已取消').length, sellCount validSchedules.filter(s = s.type === '交車' && s.status !== '已取消').length }; }, [validSchedules]); --- 統計數據 (日曆用:當月) --- const calendarStats = useMemo(() = { const monthPrefix = `${currentMonth.getFullYear()}-${String(currentMonth.getMonth() + 1).padStart(2, '0')}`; const monthItems = validSchedules.filter(s = s.date.startsWith(monthPrefix) && s.status !== '已取消'); return { viewCount monthItems.filter(s = s.type === '約看').length, buyCount monthItems.filter(s = s.type === '收車').length, sellCount monthItems.filter(s = s.type === '交車').length }; }, [validSchedules, currentMonth]); --- 操作功能 --- const openFormModal = () = { setFormData({ ...initialForm, date viewMode === 'calendar' selectedDate todayStr }); setFormType('約看'); setIsFormModalOpen(true); }; const saveSchedule = (e) = { e.preventDefault(); if (!formData.time) return alert(時間為必填欄位!); if (formType === '交車' && !formData.amount) return alert(交車必須填寫收款金額!); const newItem = { id `s${Date.now()}`, type formType, ...formData, status '待處理' }; setSchedules([...schedules, newItem]); setIsFormModalOpen(false); if (viewMode === 'calendar') { setSelectedDate(formData.date); const newDateObj = new Date(formData.date); if (newDateObj.getMonth() !== currentMonth.getMonth() newDateObj.getFullYear() !== currentMonth.getFullYear()) { setCurrentMonth(new Date(newDateObj.getFullYear(), newDateObj.getMonth(), 1)); } } }; const openStatusModal = (item) = { setSelectedItem(item); setStatusUpdateData({ status item.status, remarks item.notes '' }); setIsStatusModalOpen(true); }; const updateStatus = (e) = { e.preventDefault(); setSchedules(schedules.map(app = app.id === selectedItem.id { ...app, status statusUpdateData.status, notes statusUpdateData.remarks } app )); setIsStatusModalOpen(false); }; const handleDelete = () = { setSchedules(schedules.filter(s = s.id !== selectedItem.id)); setIsDeleteConfirmOpen(false); setIsStatusModalOpen(false); }; --- 輔助 UI 函式 --- const prevMonth = () = setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1)); const nextMonth = () = setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1)); const getTypeIcon = (type) = { if (type === '約看') return Eye size={16} ; if (type === '收車') return Truck size={16} ; if (type === '交車') return Check size={16} ; return CalendarIcon size={16} ; }; const getTypeColor = (type) = { if (type === '約看') return 'bg-blue-100 text-blue-700 border-blue-200'; if (type === '收車') return 'bg-orange-100 text-orange-700 border-orange-200'; if (type === '交車') return 'bg-emerald-100 text-emerald-700 border-emerald-200'; return 'bg-slate-100 text-slate-700 border-slate-200'; }; --- 產生行事曆網格 --- const renderCalendarGrid = () = { const year = currentMonth.getFullYear(); const month = currentMonth.getMonth(); const daysInMonth = new Date(year, month + 1, 0).getDate(); const firstDay = new Date(year, month, 1).getDay(); const blanks = Array.from({ length firstDay }).map((_, i) = ( div key={`blank-${i}`} className=p-1 min-h-[85px] mdmin-h-[130px] bg-transparent border-transparentdiv )); const days = Array.from({ length daysInMonth }).map((_, i) = { const dateNum = i + 1; const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(dateNum).padStart(2, '0')}`; const dayEvents = validSchedules.filter(s = s.date === dateStr); const isSelected = selectedDate === dateStr; const isToday = dateStr === todayStr; return ( div key={dateNum} onClick={() = setSelectedDate(dateStr)} className={`p-1 mdp-2.5 min-h-[85px] mdmin-h-[130px] rounded-[1rem] mdrounded-2xl border transition-all duration-300 cursor-pointer flex flex-col gap-0.5 mdgap-1 overflow-hidden ${isSelected 'bg-indigo-5080 border-indigo-400 shadow-md shadow-indigo-50010 ring-2 mdring-4 ring-indigo-50' 'bg-white border-slate-100 hoverborder-indigo-200 hovershadow-sm'}`} div className=flex justify-between items-start px-0.5 mdpx-0 pt-0.5 mdpt-0 span className={`text-[11px] mdtext-sm font-black w-5 h-5 mdw-8 mdh-8 flex items-center justify-center rounded-full transition-colors ${isToday 'bg-indigo-600 text-white shadow-md shadow-indigo-50030' isSelected 'bg-indigo-20050 text-indigo-800' 'text-slate-600'}`} {dateNum} span div div className=flex flex-col gap-[2px] mdgap-1.5 mt-0.5 mdmt-2 {dayEvents.slice(0, 3).map(evt = ( div key={evt.id} className={`w-full overflow-hidden px-1 mdpx-2 py-[2px] mdpy-1.5 rounded mdrounded-md flex items-center ${getTypeColor(evt.type)} ${evt.status === '已完成' evt.status === '已取消' 'opacity-40' ''}`} span className=font-black text-[10px] mdtext-[12px] opacity-95 mr-1 mdmr-1.5 shrink-0 tracking-tighter leading-none pt-[1px]{evt.time}span span className=truncate text-[9px] mdtext-[11px] font-bold leading-none pt-[1px]{evt.type === '約看' evt.name evt.type === '收車' (evt.vehicleModel evt.name) evt.location}span div ))} {dayEvents.length 3 && div className=text-[8px] mdtext-xs font-black text-slate-400 pl-1 pt-0.5+{dayEvents.length - 3} 更多div} div div ); }); return [...blanks, ...days]; }; --- 渲染內容 --- return ( div className=flex min-h-screen bg-slate-5050 overflow-x-hidden font-sans text-[15px] text-slate-900 selectionbg-indigo-200 { --- Desktop Sidebar --- } aside className=hidden lgflex flex-col w-72 h-screen bg-slate-900 text-white z-40 border-r border-slate-800 shadow-[4px_0_24px_rgba(0,0,0,0.2)] fixed top-0 left-0 div className=p-7 flex items-center gap-3 border-b border-slate-80080 div className=bg-indigo-600 p-2.5 rounded-xl shadow-lg shadow-indigo-60020 CalendarIcon size={24} color=white div span className=font-black text-xl tracking-tighter uppercase行程系統span div div className=p-7 button onClick={openFormModal} className=w-full bg-indigo-600 hoverbg-indigo-500 text-white px-5 py-4 rounded-2xl font-black text-sm shadow-xl shadow-indigo-50020 activescale-95 transition-all flex items-center justify-center gap-2 Plus size={20} 新增排程 button div div className=mt-auto p-7 border-t border-slate-80080 bg-slate-90050 {viewMode === 'calendar' ( p className=text-[11px] text-slate-400 font-black uppercase tracking-[0.2em] mb-4{currentMonth.getMonth() + 1}月 預測與統計p div className=space-y-4 div className=flex justify-between items-centerspan className=text-sm font-bold text-slate-300有效約看spanspan className=text-lg font-black text-blue-400{calendarStats.viewCount}spandiv div className=flex justify-between items-centerspan className=text-sm font-bold text-slate-300準備收車spanspan className=text-lg font-black text-orange-400{calendarStats.buyCount}spandiv div className=flex justify-between items-centerspan className=text-sm font-bold text-slate-300外送交車spanspan className=text-lg font-black text-emerald-400{calendarStats.sellCount}spandiv div ) ( p className=text-[11px] text-slate-400 font-black uppercase tracking-[0.2em] mb-2今日待辦總數p p className=text-4xl font-black text-emerald-400 tracking-tighter{listStats.todayCount} span className=text-base font-bold text-slate-500件spanp )} div aside { --- Main Content --- } main className=flex-1 lgml-72 w-full transition-all min-w-0 flex flex-col min-h-screen relative { --- 頂部導航列 (玻璃材質) --- } header className=bg-white80 backdrop-blur-xl border-b border-slate-20060 sticky top-0 z-30 px-4 py-3.5 lgpx-8 lgpy-5 flex justify-between items-center shadow-sm div className=flex items-center gap-2 lghidden CalendarIcon className=text-indigo-600 size={24} h1 className=font-black text-lg tracking-tighter text-slate-800行程看板h1 div div className=hidden lgflex items-center gap-3 div className=text-slate-400 font-black text-xs uppercase tracking-[0.2em]YN Moto Schedule Hubdiv div div className=flex items-center gap-3 mdgap-4 { 視圖切換器 (Desktop) } div className=hidden mdflex bg-slate-10080 p-1.5 rounded-2xl items-center border border-slate-20050 shadow-inner button onClick={() = setViewMode('list')} className={`px-5 py-2 rounded-xl text-xs font-black transition-all duration-300 flex items-center gap-2 ${viewMode === 'list' 'bg-white shadow-md shadow-slate-20050 text-indigo-600 scale-[1.02]' 'text-slate-500 hovertext-slate-800 hoverbg-slate-20050'}`} LayoutList size={16} 列表看板 button button onClick={() = setViewMode('calendar')} className={`px-5 py-2 rounded-xl text-xs font-black transition-all duration-300 flex items-center gap-2 ${viewMode === 'calendar' 'bg-white shadow-md shadow-slate-20050 text-indigo-600 scale-[1.02]' 'text-slate-500 hovertext-slate-800 hoverbg-slate-20050'}`} CalendarIcon size={16} 日曆總覽 button div { 新增按鈕 (確保在網頁版、平板版與手機版都常駐顯示) } button onClick={openFormModal} className=bg-indigo-600 hoverbg-indigo-500 text-white p-2.5 mdpx-4 mdpy-2.5 rounded-xl shadow-lg shadow-indigo-50030 activescale-95 transition-all flex items-center justify-center gap-1.5 shrink-0 Plus size={20} span className=hidden mdinline font-black text-sm新增span button div header div className=p-3 mdp-6 lgp-10 max-w-[1400px] mx-auto w-full flex-1 { 視圖切換器 (Mobile) } div className=mdhidden flex bg-slate-20060 p-1.5 rounded-[1rem] items-center shadow-inner mb-4 button onClick={() = setViewMode('list')} className={`flex-1 py-2 rounded-lg text-[11px] font-black transition-all duration-300 flex items-center justify-center gap-1.5 ${viewMode === 'list' 'bg-white shadow-sm text-indigo-600 scale-[1.02]' 'text-slate-500'}`} LayoutList size={14} 列表 button button onClick={() = setViewMode('calendar')} className={`flex-1 py-2 rounded-lg text-[11px] font-black transition-all duration-300 flex items-center justify-center gap-1.5 ${viewMode === 'calendar' 'bg-white shadow-sm text-indigo-600 scale-[1.02]' 'text-slate-500'}`} CalendarIcon size={14} 日曆 button div {viewMode === 'list' ( ========================================= 模式 A 列表視圖 ========================================= div className=space-y-5 mdspace-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500 { KPI 卡片 } div className=grid grid-cols-2 lggrid-cols-4 gap-2.5 mdgap-5 div className=bg-gradient-to-br from-white to-slate-50 p-4 mdp-6 rounded-[1.2rem] mdrounded-[2rem] border border-slate-100 shadow-sm flex flex-col justify-center p className=text-[9px] mdtext-xs text-slate-400 font-black mb-0.5 mdmb-1 uppercase tracking-widest truncate今日排程p p className=text-xl mdtext-3xl font-black text-slate-800 tracking-tighter{listStats.todayCount} span className=text-xs mdtext-sm text-slate-400件spanp div div className=bg-gradient-to-br from-blue-5050 to-white p-4 mdp-6 rounded-[1.2rem] mdrounded-[2rem] border border-blue-100 shadow-sm flex flex-col justify-center p className=text-[9px] mdtext-xs text-blue-500 font-black mb-0.5 mdmb-1 uppercase tracking-widest truncate有效約看p p className=text-xl mdtext-3xl font-black text-blue-700 tracking-tighter{listStats.viewCount} span className=text-xs mdtext-sm text-blue-400組spanp div div className=bg-gradient-to-br from-orange-5050 to-white p-4 mdp-6 rounded-[1.2rem] mdrounded-[2rem] border border-orange-100 shadow-sm flex flex-col justify-center p className=text-[9px] mdtext-xs text-orange-500 font-black mb-0.5 mdmb-1 uppercase tracking-widest truncate準備收車p p className=text-xl mdtext-3xl font-black text-orange-700 tracking-tighter{listStats.buyCount} span className=text-xs mdtext-sm text-orange-400台spanp div div className=bg-gradient-to-br from-emerald-5050 to-white p-4 mdp-6 rounded-[1.2rem] mdrounded-[2rem] border border-emerald-100 shadow-sm flex flex-col justify-center p className=text-[9px] mdtext-xs text-emerald-500 font-black mb-0.5 mdmb-1 uppercase tracking-widest truncate準備交車p p className=text-xl mdtext-3xl font-black text-emerald-700 tracking-tighter{listStats.sellCount} span className=text-xs mdtext-sm text-emerald-400台spanp div div { 篩選工具列 } div className=space-y-2.5 mdspace-y-3 div className=relative shadow-sm rounded-[1.2rem] mdrounded-[1.5rem] Search size={18} className=absolute left-4 mdleft-5 top-12 -translate-y-12 text-slate-400 input type=text placeholder=搜尋車款、人名、地點... className=w-full pl-10 mdpl-12 pr-4 py-3 mdpy-4 rounded-[1.2rem] mdrounded-[1.5rem] bg-white border border-slate-200 font-bold outline-none focusborder-indigo-400 focusring-2 mdfocusring-4 focusring-indigo-50010 transition-all text-sm mdtext-[15px] value={searchTerm} onChange={e = setSearchTerm(e.target.value)} div div className=flex gap-2 overflow-x-auto pb-1.5 mdpb-2 style={{ scrollbarWidth 'none' }} div className=flex bg-slate-20060 p-1 mdp-1.5 rounded-xl mdrounded-2xl gap-1 flex-nowrap items-center shrink-0 div className=px-2.5 mdpx-3 border-r border-slate-30050 shrink-0Filter size={14} className=text-slate-500 div {['全部', '約看', '收車', '交車'].map(t = ( button key={t} type=button onClick={() = setTypeFilter(t)} className={`px-4 mdpx-5 py-2 mdpy-2.5 rounded-lg mdrounded-xl text-[11px] mdtext-xs font-black transition-all shrink-0 whitespace-nowrap ${typeFilter === t 'bg-white text-slate-800 shadow-sm scale-[1.02]' 'text-slate-500 hovertext-slate-700 hoverbg-slate-20050'}`}{t}button ))} div div className=flex bg-slate-20060 p-1 mdp-1.5 rounded-xl mdrounded-2xl gap-1 flex-nowrap items-center shrink-0 shadow-inner pr-1.5 mdpr-2 div className=px-2.5 mdpx-3 border-r border-slate-30050 font-black text-[9px] mdtext-[10px] text-slate-400 uppercase tracking-widest shrink-0排序div button type=button onClick={() = setSortOrder('newest')} className={`px-4 mdpx-5 py-2 mdpy-2.5 rounded-lg mdrounded-xl text-[11px] mdtext-xs font-black transition-all shrink-0 whitespace-nowrap ${sortOrder === 'newest' 'bg-white text-indigo-600 shadow-sm scale-[1.02]' 'text-slate-500 hovertext-slate-700 hoverbg-slate-20050'}`}最近button button type=button onClick={() = setSortOrder('oldest')} className={`px-4 mdpx-5 py-2 mdpy-2.5 rounded-lg mdrounded-xl text-[11px] mdtext-xs font-black transition-all shrink-0 whitespace-nowrap ${sortOrder === 'oldest' 'bg-white text-indigo-600 shadow-sm scale-[1.02]' 'text-slate-500 hovertext-slate-700 hoverbg-slate-20050'}`}未來button div div div { 行程列表 } div className=grid grid-cols-1 gap-3.5 mdgap-4 max-w-5xl {filteredSchedules.length === 0 ( div className=text-center py-12 mdpy-16 bg-white50 border border-slate-200 border-dashed rounded-[1.5rem] mdrounded-[2.5rem] CalendarIcon size={40} className=mx-auto text-slate-300 mb-3 p className=text-slate-500 font-bold text-base目前沒有符合條件的行程紀錄p div ) ( filteredSchedules.map(item = { const isPast = new Date(`${item.date}T${item.time}`) new Date(); return ( div key={item.id} className={`group relative bg-white p-3 mdp-4 rounded-2xl mdrounded-[1.5rem] border shadow-sm flex flex-row items-center gap-3 mdgap-4 transition-all duration-300 hovershadow-md ${isPast && item.status === '待處理' 'border-red-200 shadow-red-5005' 'border-slate-100'} ${item.status === '已取消' 'opacity-60 grayscale-[0.5]' ''}`} { 刪除按鈕 X (高度還原截圖角落淡色設計) } button type=button onClick={(e) = { e.stopPropagation(); setSelectedItem(item); setIsDeleteConfirmOpen(true); }} className=absolute top-2.5 right-2.5 mdtop-3 mdright-3 p-1 text-slate-200 hovertext-red-500 hoverbg-red-50 rounded-lg transition-all activescale-95 title=刪除 X size={16} button { 左側:時間標籤 (固定寬度,垂直置中) } div className={`w-[75px] mdw-[90px] shrink-0 flex flex-col justify-center items-center py-3 mdpy-4 rounded-xl mdrounded-2xl border ${getTypeColor(item.type)}`} div className=opacity-60 mb-0.5 scale-90 mdscale-100{getTypeIcon(item.type)}div p className=font-black text-xl mdtext-2xl tracking-tighter leading-none{item.time}p p className=text-[10px] mdtext-[11px] font-bold mt-1 opacity-70 tracking-widest{item.date.substring(5)}p div { 中間:詳細資訊 } div className=flex-1 min-w-0 py-1 div className=flex items-center gap-2 mb-1.5 pr-6 span className=text-[10px] font-black px-2 py-0.5 rounded-full bg-slate-100 text-slate-500{item.status}span h3 className=font-black text-slate-800 text-base mdtext-lg leading-tight truncate {item.vehicleModel '未指定車款'} h3 div div className=flex flex-wrap items-center gap-x-3 gap-y-1.5 text-[11px] mdtext-[12px] font-bold text-slate-500 {item.name && span className=flex items-center gap-1 text-slate-600User size={12} className=text-slate-400 {item.name}span} {item.phone && span className=flex items-center gap-1 text-slate-600Phone size={12} className=text-slate-400 {item.phone}span} {item.location && ( a href={`httpswww.google.commapssearchapi=1&query=${encodeURIComponent(item.location)}`} target=_blank rel=noopener noreferrer className=flex items-center gap-1 text-slate-600 hovertext-indigo-600 transition-colors cursor-pointer underline decoration-slate-200 underline-offset-4 MapPin size={12} className=text-slate-400 {item.location} a )} {item.amount && span className=flex items-center gap-1 text-emerald-700 bg-emerald-50 px-1.5 py-0.5 rounded border border-emerald-100DollarSign size={12} className=text-emerald-500 收款 ${(Number(item.amount)0).toLocaleString()}span} div {item.notes && ( div className=mt-2 flex items-center gap-1.5 text-[11px] mdtext-[12px] font-bold text-slate-500 span className=bg-slate-100 text-slate-400 px-1.5 py-0.5 rounded-md font-black text-[10px] shrink-0備註span span className=truncate{item.notes}span div )} div { 右側:處理進度按鈕 (強制定置右側) } div className=shrink-0 pl-1 mdpl-2 button type=button onClick={() = openStatusModal(item)} className=px-3 py-2 mdpx-4 mdpy-2.5 bg-white text-slate-600 hovertext-indigo-600 hoverbg-slate-50 rounded-lg border border-slate-200 transition-all flex items-center gap-1.5 font-black text-[11px] mdtext-[12px] shadow-sm activescale-95 whitespace-nowrap Settings size={14} className=text-slate-400 處理進度 button div div ); }) )} div div ) ( ========================================= 模式 B 日曆視圖 ========================================= div className=grid grid-cols-1 lggrid-cols-12 gap-5 lggap-8 items-start animate-in fade-in slide-in-from-bottom-4 duration-500 { 左半部:日曆網格 } div className=lgcol-span-7 bg-white rounded-[1.5rem] mdrounded-[2rem] border border-slate-100 shadow-sm p-3 mdp-8 div className=flex justify-between items-center mb-4 mdmb-8 px-1 mdpx-0 h2 className=text-xl mdtext-3xl font-black text-slate-800 tracking-tighter {currentMonth.getFullYear()}年 span className=text-indigo-600{currentMonth.getMonth() + 1}月span h2 div className=flex gap-1.5 mdgap-2 bg-slate-100 p-1 mdp-1.5 rounded-xl mdrounded-2xl border border-slate-20060 button onClick={prevMonth} className=p-1.5 mdp-2.5 bg-white text-slate-600 hovertext-indigo-600 shadow-sm rounded-lg mdrounded-xl transition-all activescale-95ChevronLeft size={16} button button onClick={() = setCurrentMonth(new Date(new Date().getFullYear(), new Date().getMonth(), 1))} className=px-3 mdpx-5 py-1.5 mdpy-2.5 bg-transparent text-slate-600 font-black text-[11px] mdtext-sm uppercase tracking-widest hovertext-indigo-600 transition-colors本月button button onClick={nextMonth} className=p-1.5 mdp-2.5 bg-white text-slate-600 hovertext-indigo-600 shadow-sm rounded-lg mdrounded-xl transition-all activescale-95ChevronRight size={16} button div div div className=grid grid-cols-7 gap-1 mdgap-2 mb-1.5 mdmb-3 text-center {['日', '一', '二', '三', '四', '五', '六'].map(day = ( div key={day} className=text-[10px] mdtext-xs font-black text-slate-400 py-1.5 mdpy-2 uppercase tracking-widest{day}div ))} div div className=grid grid-cols-7 gap-1 mdgap-2 {renderCalendarGrid()} div div { 右半部:點選日期的詳細清單 } div className=lgcol-span-5 space-y-4 mdspace-y-5 lgsticky lgtop-[100px] div className=bg-slate-900 rounded-[1.5rem] mdrounded-[2rem] shadow-xl shadow-slate-90010 p-5 mdp-8 border-b-[6px] mdborder-b-[8px] border-indigo-500 text-white relative overflow-hidden p className=text-[9px] mdtext-xs font-black uppercase tracking-[0.2em] text-indigo-300 mb-1.5 mdmb-2Selected Datep h3 className=text-3xl mdtext-5xl font-black tracking-tighter mb-1 {selectedDate.split('-')[1]}月{selectedDate.split('-')[2]}日 h3 p className=text-[11px] mdtext-sm font-bold text-slate-400 mt-2 mdmt-3 當日共有 span className=text-white px-1{selectedDayEvents.length}span 個排程 p CalendarIcon size={120} className=absolute -right-6 -bottom-6 text-white[0.03] pointer-events-none mdhidden CalendarIcon size={160} className=absolute -right-8 -bottom-8 text-white[0.03] pointer-events-none hidden mdblock div div className=space-y-3 mdspace-y-4 {selectedDayEvents.length === 0 ( div className=bg-white rounded-[1.5rem] mdrounded-[2rem] border border-slate-100 p-8 mdp-10 text-center shadow-sm p className=text-slate-400 font-bold mb-4 mdmb-5 text-sm mdtext-base當日無排程,或排程已被系統過濾。p button onClick={openFormModal} className=px-5 mdpx-6 py-2.5 mdpy-3 bg-indigo-50 text-indigo-700 rounded-xl font-black text-[13px] mdtext-sm hoverbg-indigo-100 transition-colors activescale-95在此日新增行程button div ) ( selectedDayEvents.map(item = { const isPast = new Date(`${item.date}T${item.time}`) new Date() && item.date === todayStr; return ( div key={item.id} className={`group relative bg-white p-3 mdp-4 rounded-2xl mdrounded-[1.5rem] border shadow-sm flex flex-row items-center gap-3 mdgap-4 transition-all duration-300 hovershadow-md ${isPast && item.status === '待處理' 'border-red-200' 'border-slate-100'} ${item.status === '已取消' 'opacity-60 grayscale-[0.5]' ''}`} { 刪除按鈕 X (高度還原截圖角落淡色設計) } button type=button onClick={(e) = { e.stopPropagation(); setSelectedItem(item); setIsDeleteConfirmOpen(true); }} className=absolute top-2.5 right-2.5 mdtop-3 mdright-3 p-1 text-slate-200 hovertext-red-500 hoverbg-red-50 rounded-lg transition-all activescale-95 title=刪除 X size={16} button { 左側:時間標籤 (固定寬度,垂直置中) } div className={`w-[75px] mdw-[90px] shrink-0 flex flex-col justify-center items-center py-3 mdpy-4 rounded-xl mdrounded-2xl border ${getTypeColor(item.type)}`} div className=opacity-60 mb-0.5 scale-90 mdscale-100{getTypeIcon(item.type)}div p className=font-black text-xl mdtext-2xl tracking-tighter leading-none{item.time}p p className=text-[10px] mdtext-[11px] font-bold mt-1 opacity-70 tracking-widest{item.date.substring(5)}p div { 中間:詳細資訊 } div className=flex-1 min-w-0 py-1 div className=flex items-center gap-2 mb-1.5 pr-6 span className=text-[10px] font-black px-2 py-0.5 rounded-full bg-slate-100 text-slate-500{item.status}span h3 className=font-black text-slate-800 text-base mdtext-lg leading-tight truncate {item.vehicleModel '未指定車款'} h3 div div className=flex flex-wrap items-center gap-x-3 gap-y-1.5 text-[11px] mdtext-[12px] font-bold text-slate-500 {item.name && span className=flex items-center gap-1 text-slate-600User size={12} className=text-slate-400 {item.name}span} {item.phone && span className=flex items-center gap-1 text-slate-600Phone size={12} className=text-slate-400 {item.phone}span} {item.location && ( a href={`httpswww.google.commapssearchapi=1&query=${encodeURIComponent(item.location)}`} target=_blank rel=noopener noreferrer className=flex items-center gap-1 text-slate-600 hovertext-indigo-600 transition-colors cursor-pointer underline decoration-slate-200 underline-offset-4 MapPin size={12} className=text-slate-400 {item.location} a )} {item.amount && span className=flex items-center gap-1 text-emerald-700 bg-emerald-50 px-1.5 py-0.5 rounded border border-emerald-100DollarSign size={12} className=text-emerald-500 收款 ${(Number(item.amount)0).toLocaleString()}span} div {item.notes && ( div className=mt-2 flex items-center gap-1.5 text-[11px] mdtext-[12px] font-bold text-slate-500 span className=bg-slate-100 text-slate-400 px-1.5 py-0.5 rounded-md font-black text-[10px] shrink-0備註span span className=truncate{item.notes}span div )} div { 右側:處理進度按鈕 (強制定置右側) } div className=shrink-0 pl-1 mdpl-2 button type=button onClick={() = openStatusModal(item)} className=px-3 py-2 mdpx-4 mdpy-2.5 bg-white text-slate-600 hovertext-indigo-600 hoverbg-slate-50 rounded-lg border border-slate-200 transition-all flex items-center gap-1.5 font-black text-[11px] mdtext-[12px] shadow-sm activescale-95 whitespace-nowrap Settings size={14} className=text-slate-400 處理進度 button div div ); }) )} div div div )} div main { ======================================================== 🏆 模態框 (彈窗表單保持原樣,僅調整樣式細節) ======================================================== } { 1. 新增行程 Form Modal } {isFormModalOpen && ( div className=fixed inset-0 bg-slate-90040 backdrop-blur-md z-[60] flex items-end smitems-center justify-center smp-4 animate-in fade-in duration-200 form onSubmit={saveSchedule} className=bg-white w-full max-w-2xl rounded-t-[2rem] smrounded-[2.5rem] flex flex-col max-h-[90vh] smmax-h-[85vh] shadow-2xl animate-in slide-in-from-bottom-8 duration-300 overflow-hidden relative border border-slate-100 div className=relative flex items-center p-6 border-b border-slate-100 shrink-0 bg-white80 backdrop-blur-sm z-10 h2 className=text-xl smtext-2xl font-black text-slate-800 tracking-widest pr-14新增 span className=text-indigo-600{formData.date}span 排程h2 button type=button onClick={() = setIsFormModalOpen(false)} className=absolute right-5 top-5 p-2.5 bg-slate-100 hoverbg-slate-200 rounded-full text-slate-600 activescale-95 transition-all shrink-0 X size={20} button div div className=p-5 smp-8 overflow-y-auto overflow-x-hidden flex-1 bg-slate-5050 style={{ overscrollBehavior 'contain' }} div className=space-y-6 mdspace-y-8 { 行程類型選擇 } div label className=text-[11px] font-black text-slate-400 ml-1 mb-2 block uppercase tracking-[0.2em]選擇行程類型label div className=flex bg-slate-20060 p-1.5 rounded-2xl gap-1 {['約看', '收車', '交車'].map(t = ( button key={t} type=button onClick={() = setFormType(t)} className={`flex-1 py-3.5 rounded-xl text-sm font-black transition-all flex items-center justify-center gap-2 ${formType === t 'bg-white shadow-sm text-slate-800 scale-[1.02]' 'text-slate-500 hovertext-slate-700'}`} {getTypeIcon(t)} {t} button ))} div div div className=bg-white p-6 rounded-[1.5rem] mdrounded-[2rem] border border-slate-100 shadow-sm space-y-5 div className=grid grid-cols-2 gap-5 div label className=text-[11px] font-bold text-slate-500 ml-1 mb-1.5 block日期 label input required type=date className=w-full p-4 bg-slate-50 rounded-2xl font-bold border border-slate-200 outline-none focusborder-indigo-400 focusring-4 focusring-indigo-50010 transition-all value={formData.date} onChange={e = setFormData({...formData, date e.target.value})} div div label className=text-[11px] font-bold text-indigo-600 ml-1 mb-1.5 block時間 (必填) label input required type=time className=w-full p-4 bg-indigo-50 text-indigo-800 rounded-2xl font-black border border-indigo-200 outline-none focusborder-indigo-500 focusring-4 focusring-indigo-50020 transition-all value={formData.time} onChange={e = setFormData({...formData, time e.target.value})} div div div label className=text-[11px] font-bold text-slate-500 ml-1 mb-1.5 block車款 (選填)label input type=text placeholder=輸入車型名稱... className=w-full p-4 bg-slate-50 rounded-2xl font-bold border border-slate-200 outline-none focusborder-indigo-400 focusring-4 focusring-indigo-50010 transition-all value={formData.vehicleModel} onChange={e = setFormData({...formData, vehicleModel e.target.value})} div div className=grid grid-cols-2 gap-5 div label className=text-[11px] font-bold text-slate-500 ml-1 mb-1.5 block{formType === '收車' '人名' '客人名字'} (選填)label input type=text placeholder=姓名稱呼 className=w-full p-4 bg-slate-50 rounded-2xl font-bold border border-slate-200 outline-none focusborder-indigo-400 focusring-4 focusring-indigo-50010 transition-all value={formData.name} onChange={e = setFormData({...formData, name e.target.value})} div div label className=text-[11px] font-bold text-slate-500 ml-1 mb-1.5 block聯絡電話 (選填)label input type=tel placeholder=09XX-XXX-XXX className=w-full p-4 bg-slate-50 rounded-2xl font-bold border border-slate-200 outline-none focusborder-indigo-400 focusring-4 focusring-indigo-50010 transition-all value={formData.phone} onChange={e = setFormData({...formData, phone e.target.value})} div div {(formType === '收車' formType === '交車') && ( div label className=text-[11px] font-bold text-slate-500 ml-1 mb-1.5 flex items-center justify-between span地點 (選填)span {formType === '交車' && span className=text-orange-500 text-[10px] font-black tracking-wide輸入「店內」系統將自動隱藏span} label div className=relative MapPin size={18} className=absolute left-4 top-12 -translate-y-12 text-slate-400 input type=text placeholder={formType === '交車' 如:高雄市。若不填或填『店內』將隱藏 約定地點或地址} className=w-full pl-11 pr-5 py-4 bg-slate-50 rounded-2xl font-bold border border-slate-200 outline-none focusborder-indigo-400 focusring-4 focusring-indigo-50010 transition-all value={formData.location} onChange={e = setFormData({...formData, location e.target.value})} div div )} {formType === '交車' && ( div className=animate-in fade-in zoom-in-95 duration-200 label className=text-[11px] font-bold text-emerald-600 ml-1 mb-1.5 block收款多少 (必填) label div className=relative DollarSign size={20} className=absolute left-4 top-12 -translate-y-12 text-emerald-600 input required type=number placeholder=輸入金額 className=w-full pl-11 pr-5 py-4 bg-emerald-50 text-emerald-800 rounded-2xl font-black border border-emerald-200 outline-none focusborder-emerald-500 focusring-4 focusring-emerald-50020 transition-all text-xl value={formData.amount} onChange={e = setFormData({...formData, amount e.target.value})} div div )} div label className=text-[11px] font-bold text-slate-500 ml-1 mb-1.5 block備註 (選填)label textarea className=w-full p-4 bg-slate-50 rounded-2xl font-bold text-[15px] border border-slate-200 outline-none focusborder-indigo-400 focusring-4 focusring-indigo-50010 resize-none transition-all rows=3 placeholder=細節紀錄... value={formData.notes} onChange={e = setFormData({...formData, notes e.target.value})} div div div div div className=p-5 smp-6 border-t border-slate-100 bg-white shrink-0 pb-8 smpb-6 flex gap-4 button type=button onClick={() = setIsFormModalOpen(false)} className=flex-1 bg-slate-100 text-slate-600 py-4 rounded-2xl font-black text-lg activescale-[0.98] hoverbg-slate-200 transition-all取消button button type=submit className=flex-[2] bg-indigo-600 text-white py-4 rounded-2xl font-black text-lg hoverbg-indigo-500 transition-all shadow-xl shadow-indigo-60020 activescale-[0.98]儲存行程button div form div )} { 2. 更新進度 Modal } {isStatusModalOpen && selectedItem && ( div className=fixed inset-0 bg-slate-90040 backdrop-blur-md z-[60] flex items-end smitems-center justify-center p-0 smp-4 animate-in fade-in duration-200 form onSubmit={updateStatus} className=bg-white w-full max-w-md rounded-t-[2rem] smrounded-[2.5rem] flex flex-col max-h-[90vh] smmax-h-[85vh] shadow-2xl animate-in slide-in-from-bottom-8 duration-300 overflow-hidden relative border border-slate-100 div className=relative flex items-center p-6 border-b border-slate-100 shrink-0 bg-white h2 className=text-xl font-black flex items-center gap-2 text-slate-800 pr-14進度回報h2 button type=button onClick={() = setIsStatusModalOpen(false)} className=absolute right-5 top-5 p-2.5 bg-slate-100 hoverbg-slate-200 rounded-full text-slate-600 activescale-95 transition-all shrink-0X size={20} button div div className=p-5 smp-8 overflow-y-auto overflow-x-hidden flex-1 bg-slate-5050 space-y-6 style={{ overscrollBehavior 'contain' }} div className=bg-white p-5 rounded-[1.5rem] border border-slate-100 shadow-sm text-center mb-2 p className=text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2{selectedItem.type} • {selectedItem.date} {selectedItem.time}p p className=font-black text-slate-800 text-xl tracking-tight{selectedItem.vehicleModel '未定車款'} - {selectedItem.name '無名氏'}p div div className=grid grid-cols-3 gap-2 bg-slate-20060 p-1.5 rounded-2xl {['待處理', '已完成', '已取消'].map(s = ( button key={s} type=button onClick={() = setStatusUpdateData({...statusUpdateData, status s})} className={`py-3.5 rounded-xl text-sm font-black transition-all ${statusUpdateData.status === s (s === '已完成' 'bg-white text-emerald-600 shadow-sm scale-[1.02]' s === '已取消' 'bg-white text-slate-400 shadow-sm scale-[1.02]' 'bg-white text-orange-600 shadow-sm scale-[1.02]') 'text-slate-500 hovertext-slate-700 hoverbg-slate-20050'}`}{s}button ))} div div className=pt-2 label className=text-[11px] font-bold text-slate-500 ml-1 mb-1.5 block後續備註更新label textarea className=w-full p-5 bg-white rounded-2xl font-bold text-[15px] border border-slate-200 shadow-sm outline-none focusborder-indigo-400 focusring-4 focusring-indigo-50010 resize-none transition-all rows=4 placeholder=紀錄最終結果或重點... value={statusUpdateData.remarks} onChange={e = setStatusUpdateData({...statusUpdateData, remarks e.target.value})} div div div className=p-5 smp-6 border-t border-slate-100 bg-white shrink-0 pb-8 smpb-6 flex gap-3 button type=button onClick={() = setIsDeleteConfirmOpen(true)} className=w-[60px] smw-[70px] shrink-0 border-2 border-red-100 text-red-500 bg-red-50 hoverbg-red-500 hovertext-white rounded-2xl transition-all flex items-center justify-center activescale-95 title=刪除 Trash2 size={24} button button type=button onClick={() = setIsStatusModalOpen(false)} className=flex-1 bg-slate-100 text-slate-600 py-4 rounded-2xl font-black text-lg activescale-[0.98] hoverbg-slate-200 transition-all取消button button type=submit className={`flex-[2] py-4 rounded-2xl font-black text-lg transition-all shadow-xl activescale-[0.98] ${statusUpdateData.status === '已完成' 'bg-emerald-600 hoverbg-emerald-500 text-white shadow-emerald-60020' statusUpdateData.status === '已取消' 'bg-slate-800 hoverbg-slate-700 text-white shadow-slate-90020' 'bg-orange-600 hoverbg-orange-500 text-white shadow-orange-60020'}`}確認更新button div form div )} { 3. 刪除確認 Modal } {isDeleteConfirmOpen && selectedItem && ( div className=fixed inset-0 bg-slate-90060 backdrop-blur-md z-[80] flex items-center justify-center p-4 animate-in fade-in duration-200 div className=bg-white w-full max-w-sm rounded-[2.5rem] p-8 shadow-2xl text-center border-b-[8px] border-red-500 relative animate-in zoom-in-95 duration-300 button type=button onClick={() = setIsDeleteConfirmOpen(false)} className=absolute top-5 right-5 z-[100] p-2 bg-slate-50 hoverbg-slate-100 rounded-full text-slate-400 hovertext-slate-600 transition-all activescale-95 shrink-0 X size={20} button div className=bg-red-50 w-24 h-24 rounded-full flex items-center justify-center mx-auto mb-6 mt-2 ring-8 ring-red-5050 Trash2 size={40} className=text-red-500 div h2 className=text-2xl font-black text-slate-800 mb-3 tracking-tight確定要刪除嗎?h2 p className=text-slate-500 font-bold mb-8 text-[15px] leading-relaxed 即將刪除 span className=text-slate-800 font-black{selectedItem.vehicleModel '此行程'}span。br此動作執行後將無法復原。 p div className=flex gap-4 button type=button onClick={() = setIsDeleteConfirmOpen(false)} className=flex-1 py-4 bg-slate-100 rounded-2xl font-black text-slate-600 hoverbg-slate-200 activescale-95 transition-all text-lg取消button button type=button onClick={handleDelete} className=flex-1 py-4 bg-red-600 hoverbg-red-500 rounded-2xl font-black text-white activescale-95 transition-all shadow-xl shadow-red-60020 text-lg確認刪除button div div div )} div ); }