import React, { useState, useMemo, useEffect } from 'react';
import {
Plus,
Trash2,
Edit2,
Download,
Copy,
Search,
Users,
Wallet,
MessageSquare,
Calendar,
X,
CheckCircle2,
AlertCircle,
Briefcase,
MapPin,
Heart,
Languages
} from 'lucide-react';
const App = () => {
// --- Đa ngôn ngữ / Multi-language configuration ---
const [lang, setLang] = useState('vi'); // 'vi' or 'zh'
const translations = {
vi: {
title: "Hệ Thống Báo Cáo CRM",
subtitle: "Quản lý khách hàng chuyên nghiệp",
reporterLabel: "Tên người thực hiện",
reporterPlaceholder: "Nhập tên của bạn...",
searchPlaceholder: "Tìm tên khách, phân loại...",
copy: "Sao chép",
download: "Tải file",
stats: {
total: "Tổng khách",
deposited: "Đã nạp",
responded: "Phản hồi",
roles: "Vai trò",
performance: "Hiệu suất"
},
form: {
info: "Thông tin báo cáo",
addTitle: "Thêm khách hàng",
editTitle: "Chỉnh sửa khách hàng",
name: "Tên khách hàng *",
role: "Vai trò",
responded: "Phản hồi hôm nay",
type: "Phân loại khách",
amount: "Tiền nạp",
job: "Công việc",
address: "Nơi ở",
marital: "Hôn nhân",
log: "Nhật ký hỗ trợ khách hàng...",
status: "Tình trạng hiện tại",
plan: "Dự tính ngày mai",
submitAdd: "Thêm vào danh sách",
submitEdit: "Cập nhật thông tin",
yes: "Có phản hồi",
no: "Không phản hồi"
},
table: {
listTitle: "Danh sách báo cáo chi tiết",
time: "Thời gian",
clearAll: "Xóa tất cả",
headCustomer: "Khách & Nguồn",
headType: "Phân loại & Tiền",
headLog: "Nhật ký & Dự định",
headAction: "Thao tác",
noData: "Chưa có dữ liệu khách hàng.",
responded: "Phản hồi",
silent: "Im lặng",
tomorrow: "Dự định"
},
report: {
header: "BÁO CÁO CÔNG VIỆC HÀNG NGÀY",
reporter: "Người báo cáo",
date: "Ngày báo cáo",
summary: "TỔNG HỢP SỐ LIỆU",
detail: "DANH SÁCH CHI TIẾT",
depositedCount: "Tổng khách đã nạp tiền",
respondedRatio: "Số khách phản hồi",
bySource: "Chi tiết theo nguồn",
assistant: "Trợ lý chăm",
shareholder: "Cổ đông chăm",
referral: "Khách giới thiệu"
},
msg: {
nameReq: "Vui lòng nhập tên khách hàng!",
updated: "Đã cập nhật thông tin khách hàng",
added: "Đã thêm khách hàng mới",
deleted: "Đã xóa khách hàng",
cleared: "Đã xóa toàn bộ danh sách",
copySuccess: "Đã sao chép báo cáo!",
copyFail: "Không thể sao chép!",
emptyList: "Danh sách đang trống!",
nameReportReq: "Vui lòng nhập tên người báo cáo!"
}
},
zh: {
title: "CRM 报告系统",
subtitle: "专业客户管理系统",
reporterLabel: "报告人姓名",
reporterPlaceholder: "请输入您的姓名...",
searchPlaceholder: "搜索姓名、分类...",
copy: "复制内容",
download: "导出文件",
stats: {
total: "总客户数",
deposited: "已充值",
responded: "已回复",
roles: "角色分配",
performance: "回复率"
},
form: {
info: "报告基本信息",
addTitle: "添加新客户",
editTitle: "编辑客户信息",
name: "客户姓名 *",
role: "来源角色",
responded: "今日是否回复",
type: "客户分类",
amount: "充值金额",
job: "职业",
address: "居住地",
marital: "婚姻状况",
log: "今日沟通/支持记录...",
status: "目前状态",
plan: "明日计划",
submitAdd: "添加到列表",
submitEdit: "更新信息",
yes: "已回复",
no: "未回复"
},
table: {
listTitle: "详细报告列表",
time: "时间",
clearAll: "清空全部",
headCustomer: "客户与来源",
headType: "分类与金额",
headLog: "记录与计划",
headAction: "操作",
noData: "暂无客户数据。",
responded: "已回复",
silent: "未回复",
tomorrow: "计划"
},
report: {
header: "每日工作报告",
reporter: "报告人",
date: "报告日期",
summary: "数据统计",
detail: "详细列表",
depositedCount: "总充值客户数",
respondedRatio: "回复人数",
bySource: "按来源详情",
assistant: "助理负责",
shareholder: "股东负责",
referral: "客户推荐"
},
msg: {
nameReq: "请输入客户姓名!",
updated: "客户信息已更新",
added: "已成功添加客户",
deleted: "已删除客户",
cleared: "列表已清空",
copySuccess: "报告内容已复制!",
copyFail: "复制失败!",
emptyList: "列表为空!",
nameReportReq: "请输入报告人姓名!"
}
}
};
const t = (key) => {
const keys = key.split('.');
let result = translations[lang];
for (const k of keys) {
if (result[k]) result = result[k];
else return key;
}
return result;
};
// State
const [reporterName, setReporterName] = useState('');
const [customers, setCustomers] = useState([]);
const [editId, setEditId] = useState(null);
const [searchTerm, setSearchTerm] = useState('');
const [notification, setNotification] = useState(null);
const [formData, setFormData] = useState({
role: 'Trợ lý',
type: 'Khách nạp tiền',
amount: '',
responded: 'Có',
name: '',
job: '',
finance: '',
maritalStatus: '',
address: '',
supportLog: '',
currentStatus: '',
tomorrowPlan: ''
});
// Constants (Mapped per language)
const getRoles = () => lang === 'vi' ? ['Trợ lý', 'Cổ đông', 'Người giới thiệu'] : ['助理', '股东', '推荐人'];
const getCustomerTypes = () => lang === 'vi' ? [
'Khách nạp tiền',
'Khách nạp tiền nhưng rút hết',
'Khách đăng ký chưa nạp tiền còn nói chuyện',
'Khách đăng ký chưa nạp tiền nhưng không nói chuyện',
'Khách đang chăm',
'Khách có dự định nạp tiền'
] : [
'已充值客户',
'已充值但提空客户',
'已注册未充值(沟通中)',
'已注册未充值(无沟通)',
'跟进中客户',
'有充值意向客户'
];
// Notification helper
const showNotification = (message, type = 'success') => {
setNotification({ message, type });
setTimeout(() => setNotification(null), 3000);
};
// Handlers
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const addOrUpdateCustomer = (e) => {
e.preventDefault();
if (!formData.name.trim()) {
showNotification(t('msg.nameReq'), "error");
return;
}
if (editId) {
setCustomers(customers.map(c => c.id === editId ? { ...formData, id: editId } : c));
setEditId(null);
showNotification(t('msg.updated'));
} else {
setCustomers([{ ...formData, id: Date.now() }, ...customers]);
showNotification(t('msg.added'));
}
setFormData({
role: lang === 'vi' ? 'Trợ lý' : '助理',
type: lang === 'vi' ? 'Khách nạp tiền' : '已充值客户',
amount: '',
responded: 'Có',
name: '', job: '', finance: '', maritalStatus: '', address: '', supportLog: '', currentStatus: '', tomorrowPlan: ''
});
};
const startEdit = (c) => {
setEditId(c.id);
setFormData({ ...c });
window.scrollTo({ top: 0, behavior: 'smooth' });
};
const deleteCustomer = (id) => {
setCustomers(customers.filter(c => c.id !== id));
showNotification(t('msg.deleted'), "info");
};
const clearAll = () => {
if (customers.length === 0) return;
setCustomers([]);
showNotification(t('msg.cleared'), "info");
};
// Memoized stats
const filteredCustomers = useMemo(() => {
return customers.filter(c =>
c.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
c.type.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [customers, searchTerm]);
const stats = useMemo(() => {
const total = customers.length;
const respondedCount = customers.filter(c => c.responded === 'Có' || c.responded === '已回复').length;
const depositedCount = customers.filter(c => c.type.includes('nạp tiền') || c.type.includes('充值')).length;
return {
total,
respondedCount,
depositedCount,
assistant: customers.filter(c => c.role === 'Trợ lý' || c.role === '助理').length,
shareholder: customers.filter(c => c.role === 'Cổ đông' || c.role === '股东').length,
referral: customers.filter(c => c.role === 'Người giới thiệu' || c.role === '推荐人').length
};
}, [customers]);
const generateReportText = () => {
if (!reporterName) {
showNotification(t('msg.nameReportReq'), "error");
return null;
}
if (customers.length === 0) {
showNotification(t('msg.emptyList'), "error");
return null;
}
const tr = t('report');
let text = `==========================================\n`;
text += `${tr.header}\n`;
text += `==========================================\n`;
text += `${tr.reporter}: ${reporterName}\n`;
text += `${tr.date}: ${new Date().toLocaleDateString(lang === 'vi' ? 'vi-VN' : 'zh-CN')}\n\n`;
text += `${tr.summary}:\n`;
text += `------------------------------------------\n`;
text += `- ${t('stats.total')}: ${stats.total}\n`;
text += `- ${tr.depositedCount}: ${stats.depositedCount}\n`;
text += `- ${tr.respondedRatio}: ${stats.respondedCount} / ${stats.total}\n`;
text += `- ${tr.bySource}:\n`;
text += ` + ${tr.assistant}: ${stats.assistant}\n`;
text += ` + ${tr.shareholder}: ${stats.shareholder}\n`;
text += ` + ${tr.referral}: ${stats.referral}\n`;
text += `------------------------------------------\n\n`;
text += `${tr.detail}:\n`;
text += `==========================================\n\n`;
customers.forEach((c, index) => {
text += `${index + 1}. ${t('form.name').replace(' *', '')}: ${c.name} (${c.role})\n`;
text += ` - ${t('form.type')}: ${c.type}\n`;
text += ` - ${t('form.amount')}: ${c.amount || '0'}\n`;
text += ` - ${t('form.responded')}: ${c.responded}\n`;
text += ` - ${t('table.headLog').split(' & ')[0]}: ${c.supportLog || '-'}\n`;
text += ` - ${t('form.status')}: ${c.currentStatus || '-'}\n`;
text += ` - ${t('form.plan')}: ${c.tomorrowPlan || '-'}\n`;
text += `------------------------------------------\n`;
});
return text;
};
const exportReport = () => {
const text = generateReportText();
if (!text) return;
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const prefix = lang === 'vi' ? 'BaoCao' : '工作报告';
a.download = `${prefix}_${reporterName.replace(/\s+/g, '_')}_${new Date().toLocaleDateString('vi-VN').replace(/\//g, '-')}.txt`;
a.click();
URL.revokeObjectURL(url);
};
const copyToClipboard = () => {
const text = generateReportText();
if (!text) return;
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
showNotification(t('msg.copySuccess'));
} catch (err) {
showNotification(t('msg.copyFail'), "error");
}
document.body.removeChild(textArea);
};
return (
{/* Notifications */}
{notification && (
{notification.type === 'error' ?
:
}
{notification.message}
)}
{/* Header */}
{/* Sidebar / Form */}
{editId ? : }
{editId ? t('form.editTitle') : t('form.addTitle')}
{editId && (
)}
{/* Table & Stats */}
{/* Stats Dashboard */}
{t('stats.total')}
{stats.total}
{t('stats.deposited')}
{stats.depositedCount}
{t('stats.responded')}
{stats.respondedCount}
{t('stats.roles')}
{lang === 'vi' ? 'Trợ lý' : '助理'}: {stats.assistant} | {lang === 'vi' ? 'Cổ đông' : '股东'}: {stats.shareholder}
{lang === 'vi' ? 'G.Thiệu' : '推荐'}: {stats.referral}
{t('stats.performance')}
{stats.total > 0 ? Math.round((stats.respondedCount / stats.total) * 100) : 0}%
{/* List Container */}
{t('table.listTitle')}
{t('table.time')}: {new Date().toLocaleDateString(lang === 'vi' ? 'vi-VN' : 'zh-CN')}
| {t('table.headCustomer')} |
{t('table.headType')} |
{t('table.headLog')} |
{t('table.headAction')} |
{filteredCustomers.length === 0 ? (
|
|
) : (
filteredCustomers.map(c => (
{c.name.charAt(0).toUpperCase()}
{c.name}
{c.role}
{(c.responded === 'Có' || c.responded === '已回复') ? (
{t('table.responded')}
) : (
{t('table.silent')}
)}
|
{c.type}
{c.amount || '0'}
|
{c.supportLog || '...'}
{c.currentStatus || '-'}
{t('table.tomorrow')}: {c.tomorrowPlan || '-'}
|
|
))
)}
{/* Mobile Actions */}
);
};
export default App;