import React, { useCallback, useEffect, useMemo, useState } from 'react' import { Link, useParams } from 'react-router-dom' import axios from 'axios' import { UsersIcon, ClockIcon, PlusCircleIcon, InquiryIcon, QuoteIcon, ClipboardIcon, RefreshIcon, TargetIcon, PencilIcon, CloseIcon } from '../components/Icons' import { useAuth, hasPermission } from '../auth' const ACTIVITY_TYPES = ['邮件', '电话', '微信', 'WhatsApp', '拜访', '样品', '展会', '其他'] /* 联系人表单的空值模板:新增和「清空重置」共用一份,避免字段改动漏掉某处 */ const EMPTY_CONTACT = { name: '', name_en: '', position: '', department: '', email: '', phone: '', whatsapp: '', wechat: '', is_primary: false, notes: '' } const STATUS_LABELS = { active: '活跃', inactive: '非活跃', lead: '潜在' } const INQUIRY_STATUS_LABELS = { draft: '草稿', quoting: '报价中', quoted: '已报价', won: '已成交', lost: '已流失', closed: '已关闭' } const ORDER_STATUS_LABELS = { pending: '待处理', confirmed: '已确认', producing: '生产中', shipped: '已发货', completed: '已完成', cancelled: '已取消' } function todayKey() { const now = new Date() return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}` } function money(value, currency) { const number = Number(value) if (!Number.isFinite(number)) return '—' return `${number.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}${currency ? ` ${currency}` : ''}` } function formatMoment(value) { if (!value) return '—' const date = new Date(value) if (Number.isNaN(date.getTime())) return String(value) return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) } /** 下次跟进日期的语义化提示 —— 业务上「逾期」和「今天」要一眼能看出来 */ function dueState(dateKey) { if (!dateKey) return null const today = todayKey() if (dateKey < today) return { tone: 'danger', label: '已逾期' } if (dateKey === today) return { tone: 'warn', label: '今天' } return { tone: 'ok', label: '待跟进' } } /** 快速记录跟进:默认收起成一个输入框,聚焦或输入时展开完整字段 */ function QuickFollowUp({ customer, contacts, inquiries, onSaved, canEdit }) { const [open, setOpen] = useState(false) const [form, setForm] = useState({ activity_type: ACTIVITY_TYPES[0], content: '', next_action: '', next_action_date: '', contact_id: '', inquiry_id: '' }) const [saving, setSaving] = useState(false) const [error, setError] = useState('') const submit = async (event) => { event.preventDefault() const content = form.content.trim() if (!content) { setError('写点什么吧,至少留下一句跟进内容'); return } setSaving(true) setError('') try { await axios.post(`/api/customers/${customer.id}/follow-ups`, { activity_type: form.activity_type, content, next_action: form.next_action.trim() || null, next_action_date: form.next_action_date || null, contact_id: form.contact_id ? Number(form.contact_id) : null, inquiry_id: form.inquiry_id ? Number(form.inquiry_id) : null }) setForm({ activity_type: ACTIVITY_TYPES[0], content: '', next_action: '', next_action_date: '', contact_id: '', inquiry_id: '' }) setOpen(false) onSaved() } catch (submitError) { setError(submitError?.response?.data?.error || '记录失败,请稍后重试') } finally { setSaving(false) } } return (
setOpen(true)} onChange={event => setForm({ ...form, content: event.target.value })} /> {!open && canEdit && }
{open && (
setForm({ ...form, next_action: event.target.value })} />
setForm({ ...form, next_action_date: event.target.value })} />
{inquiries.length > 0 && (
)} {error &&

{error}

}
{canEdit && ( )}
)} {!open && error &&

{error}

}
) } /** 编辑一条跟进记录 —— 字段与快速记录保持一致,避免两套表单口径不一 */ function FollowUpEditModal({ record, contacts, inquiries, onClose, onSaved }) { const [form, setForm] = useState({ activity_type: record.activity_type || ACTIVITY_TYPES[0], content: record.content || '', next_action: record.next_action || '', next_action_date: record.next_action_date ? String(record.next_action_date).slice(0, 10) : '', contact_id: record.contact_id || '', inquiry_id: record.inquiry_id || '' }) const [saving, setSaving] = useState(false) const [error, setError] = useState('') const submit = async event => { event.preventDefault() const content = form.content.trim() if (!content) { setError('跟进内容不能为空'); return } setSaving(true) setError('') try { await axios.patch(`/api/customers/${record.customer_id}/follow-ups/${record.id}`, { activity_type: form.activity_type, content, next_action: form.next_action.trim() || null, next_action_date: form.next_action_date || null, contact_id: form.contact_id ? Number(form.contact_id) : null, inquiry_id: form.inquiry_id ? Number(form.inquiry_id) : null }) onSaved() } catch (err) { setError(err?.response?.data?.error || '保存失败,请稍后重试') setSaving(false) } } return (
event.stopPropagation()}>

编辑跟进记录

{error &&

{error}

}