已生成 {lastSavedRecord.record.name} 的建檔資料檔({lastSavedRecord.fileName})。請點擊右側按鈕開啟郵件軟體寄送至 mingshiu.kao@gmail.com,並手動附加此 CSV 檔案。
```react import React, { useState, useEffect, useMemo } from 'react'; import { User, Phone, MapPin, Package, Layers, Search, Download, Plus, Edit3, Trash2, CheckCircle, Sparkles, Save, X, FileSpreadsheet, ArrowUpDown, RefreshCw, Mail, Send, Check } from 'lucide-react'; export default function App() { // 表單狀態 const [formData, setFormData] = useState({ name: '', account: '', email: '', level: '大代理', supervisorName: '', phone: '', address: '', hasDongJing: false, hasJingLu: false, dongJingQty: 0, jingLuQty: 0, }); // 紀錄列表狀態(預設載入 localStorage 或範例資料) const [records, setRecords] = useState(() => { const saved = localStorage.getItem('inventory_records'); if (saved) { try { return JSON.parse(saved); } catch (e) { console.error('Parse storage failed', e); } } return [ { id: '1', createdAt: '2026-11-10 10:30', name: '許潔絲', account: 'Jessica', email: 'jessica@example.com', level: '大代理', supervisorName: '公司直屬', phone: '098369745', address: '台南市南區水源路654號', items: ['凍晶', '晶露'], dongJingQty: 6, jingLuQty: 8, totalStock: 14 } ]; }); // 編輯模式 IDs const [editingId, setEditingId] = useState(null); // 新增成功後的 Email 寄送通知 Banner 狀態 const [lastSavedRecord, setLastSavedRecord] = useState(null); // 搜尋與篩選狀態 const [searchTerm, setSearchTerm] = useState(''); const [filterLevel, setFilterLevel] = useState('全部'); const [sortBy, setSortBy] = useState('newest'); // 'newest' | 'oldest' | 'totalDesc' // 保存至 localStorage useEffect(() => { localStorage.setItem('inventory_records', JSON.stringify(records)); }, [records]); // 計算庫存總數 (即時衍生值) const currentTotalStock = useMemo(() => { const dj = parseInt(formData.dongJingQty) || 0; const jl = parseInt(formData.jingLuQty) || 0; return dj + jl; }, [formData.dongJingQty, formData.jingLuQty]); // 當數量改變時,自動調整商品選擇狀態 const handleQtyChange = (field, value) => { const numValue = Math.max(0, parseInt(value) || 0); setFormData(prev => { const nextData = { ...prev, [field]: numValue }; if (field === 'dongJingQty') { nextData.hasDongJing = numValue > 0; } if (field === 'jingLuQty') { nextData.hasJingLu = numValue > 0; } return nextData; }); }; // 當勾選框改變時處理 const handleCheckboxChange = (field, checked) => { setFormData(prev => { const nextData = { ...prev, [field]: checked }; if (field === 'hasDongJing' && !checked) nextData.dongJingQty = 0; if (field === 'hasJingLu' && !checked) nextData.jingLuQty = 0; return nextData; }); }; // 一鍵帶入示範資料 const loadDemoData = () => { setFormData({ name: '許潔絲', account: 'Jessica', email: 'jessica@example.com', level: '大代理', supervisorName: '公司直屬', phone: '098369745', address: '台南市南區水源路654號', hasDongJing: true, hasJingLu: true, dongJingQty: 6, jingLuQty: 8, }); }; // 清空表單 const resetForm = () => { setFormData({ name: '', account: '', email: '', level: '大代理', supervisorName: '', phone: '', address: '', hasDongJing: false, hasJingLu: false, dongJingQty: 0, jingLuQty: 0, }); setEditingId(null); }; // 觸發單筆 CSV 下載 const downloadSingleCSV = (record) => { const headers = ['建檔時間', '姓名', '帳號', '電子郵件', '級別', '上級名字', '電話', '地址', '貨品項目', '凍晶(瓶)', '晶露(瓶)', '目前庫存總數']; const row = [ `"${record.createdAt || ''}"`, `"${record.name || ''}"`, `"${record.account || ''}"`, `"${record.email || ''}"`, `"${record.level || ''}"`, `"${record.supervisorName || ''}"`, `"${record.phone || ''}"`, `"${(record.address || '').replace(/"/g, '""')}"`, `"${record.items.join(' + ')}"`, record.dongJingQty, record.jingLuQty, record.totalStock ].join(','); const csvContent = '\uFEFF' + headers.join(',') + '\n' + row; const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.setAttribute('href', url); const fileName = `建檔紀錄_${record.name}_${new Date().toISOString().slice(0,10)}.csv`; link.setAttribute('download', fileName); document.body.appendChild(link); link.click(); document.body.removeChild(link); return fileName; }; // 建立郵件連結 (mailto) const getMailtoLink = (record, fileName) => { const recipient = 'mingshiu.kao@gmail.com'; const subject = encodeURIComponent(`[庫存建檔通知] 新增會員資料 - ${record.name}`); const bodyText = `你好,以下為最新建檔資料: 【基本資料】 • 建檔時間:${record.createdAt} • 姓名:${record.name} • 帳號:${record.account || '未填寫'} • 電子郵件:${record.email || '未填寫'} • 級別:${record.level} • 上級名字:${record.supervisorName || '無'} • 電話:${record.phone || '未填寫'} • 地址:${record.address || '未填寫'} 【貨品與庫存】 • 貨品品項:${record.items.join(' + ') || '無'} • 凍晶數量:${record.dongJingQty} 瓶 • 晶露數量:${record.jingLuQty} 瓶 • 庫存總數:${record.totalStock} 瓶 ---------------------------------------- *系統已自動下載單筆 CSV 檔案(${fileName || 'CSV檔案'}),請記得將檔案附加於此信件中寄出。`; return `mailto:${recipient}?subject=${subject}&body=${encodeURIComponent(bodyText)}`; }; // 表單送出處裡 const handleSubmit = (e) => { e.preventDefault(); if (!formData.name.trim()) { alert('請輸入姓名!'); return; } const items = []; if (formData.hasDongJing || formData.dongJingQty > 0) items.push('凍晶'); if (formData.hasJingLu || formData.jingLuQty > 0) items.push('晶露'); const now = new Date(); const formattedDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; if (editingId) { // 編輯更新 setRecords(prev => prev.map(item => { if (item.id === editingId) { return { ...item, name: formData.name, account: formData.account, email: formData.email, level: formData.level, supervisorName: formData.supervisorName, phone: formData.phone, address: formData.address, items: items, dongJingQty: parseInt(formData.dongJingQty) || 0, jingLuQty: parseInt(formData.jingLuQty) || 0, totalStock: currentTotalStock, }; } return item; })); setEditingId(null); } else { // 新增紀錄 const newRecord = { id: Date.now().toString(), createdAt: formattedDate, name: formData.name, account: formData.account, email: formData.email, level: formData.level, supervisorName: formData.supervisorName, phone: formData.phone, address: formData.address, items: items, dongJingQty: parseInt(formData.dongJingQty) || 0, jingLuQty: parseInt(formData.jingLuQty) || 0, totalStock: currentTotalStock, }; setRecords(prev => [newRecord, ...prev]); // 自動產生並下載 CSV 檔 const downloadedFileName = downloadSingleCSV(newRecord); // 設定最新建檔紀錄觸發寄信通知 Alert setLastSavedRecord({ record: newRecord, fileName: downloadedFileName }); } resetForm(); }; // 觸發編輯 const handleEdit = (record) => { setEditingId(record.id); setFormData({ name: record.name, account: record.account, email: record.email || '', level: record.level, supervisorName: record.supervisorName || record.supervisor || '', phone: record.phone, address: record.address, hasDongJing: record.dongJingQty > 0 || record.items.includes('凍晶'), hasJingLu: record.jingLuQty > 0 || record.items.includes('晶露'), dongJingQty: record.dongJingQty, jingLuQty: record.jingLuQty, }); window.scrollTo({ top: 0, behavior: 'smooth' }); }; // 刪除紀錄 const handleDelete = (id) => { if (window.confirm('確定要刪除這筆紀錄嗎?')) { setRecords(prev => prev.filter(r => r.id !== id)); if (editingId === id) resetForm(); } }; // 篩選與排序紀錄 const filteredRecords = useMemo(() => { return records .filter(record => { const matchesSearch = record.name.toLowerCase().includes(searchTerm.toLowerCase()) || record.account.toLowerCase().includes(searchTerm.toLowerCase()) || (record.email && record.email.toLowerCase().includes(searchTerm.toLowerCase())) || (record.supervisorName && record.supervisorName.toLowerCase().includes(searchTerm.toLowerCase())) || record.phone.includes(searchTerm) || record.address.toLowerCase().includes(searchTerm.toLowerCase()); const matchesLevel = filterLevel === '全部' || record.level === filterLevel; return matchesSearch && matchesLevel; }) .sort((a, b) => { if (sortBy === 'newest') return b.id.localeCompare(a.id); if (sortBy === 'oldest') return a.id.localeCompare(b.id); if (sortBy === 'totalDesc') return b.totalStock - a.totalStock; return 0; }); }, [records, searchTerm, filterLevel, sortBy]); // 匯出全量 CSV 檔案 (加入 UTF-8 BOM 避免 Excel 亂碼) const exportToCSV = () => { if (records.length === 0) { alert('目前沒有資料可供匯出!'); return; } const headers = ['建檔時間', '姓名', '帳號', '電子郵件', '級別', '上級名字', '電話', '地址', '貨品項目', '凍晶(瓶)', '晶露(瓶)', '目前庫存總數']; const csvRows = [ headers.join(','), ...records.map(r => [ `"${r.createdAt || ''}"`, `"${r.name || ''}"`, `"${r.account || ''}"`, `"${r.email || ''}"`, `"${r.level || ''}"`, `"${r.supervisorName || r.supervisor || ''}"`, `"${r.phone || ''}"`, `"${(r.address || '').replace(/"/g, '""')}"`, `"${r.items.join(' + ')}"`, r.dongJingQty, r.jingLuQty, r.totalStock ].join(',')) ]; const csvContent = '\uFEFF' + csvRows.join('\n'); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.setAttribute('href', url); link.setAttribute('download', `全量庫存登記資料_${new Date().toISOString().slice(0, 10)}.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); }; return (
即時管理代理資料、庫存計算與報表匯出
已生成 {lastSavedRecord.record.name} 的建檔資料檔({lastSavedRecord.fileName})。請點擊右側按鈕開啟郵件軟體寄送至 mingshiu.kao@gmail.com,並手動附加此 CSV 檔案。