210 lines
7.5 KiB
TypeScript
210 lines
7.5 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
|
import { Table, Button, Modal, Form, Input, Switch, message, Space, Tag, Popconfirm } from 'antd'
|
|
import { PlusOutlined, EditOutlined, DeleteOutlined, PhoneOutlined, UserOutlined } from '@ant-design/icons'
|
|
import type { ColumnsType } from 'antd/es/table'
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
interface Contact {
|
|
id: number
|
|
name: string
|
|
name_zh?: string
|
|
position?: string
|
|
department?: string
|
|
is_primary: boolean
|
|
phone?: string
|
|
mobile?: string
|
|
wechat?: string
|
|
whatsapp?: string
|
|
line_id?: string
|
|
notes?: string
|
|
}
|
|
|
|
interface ContactManagerProps {
|
|
companyType: 'customer' | 'supplier' | 'subcontractor'
|
|
companyId: number
|
|
companyName: string
|
|
onContactsUpdated?: () => void
|
|
}
|
|
|
|
const ContactManager: React.FC<ContactManagerProps> = ({
|
|
companyType,
|
|
companyId,
|
|
companyName,
|
|
onContactsUpdated
|
|
}) => {
|
|
const { t } = useTranslation()
|
|
const [contacts, setContacts] = useState<Contact[]>([])
|
|
const [loading, setLoading] = useState(false)
|
|
const [modalVisible, setModalVisible] = useState(false)
|
|
const [editingContact, setEditingContact] = useState<Contact | null>(null)
|
|
const [form] = Form.useForm()
|
|
|
|
const fetchContacts = async () => {
|
|
setLoading(true)
|
|
try {
|
|
const response = await fetch(`/api/${companyType}s/${companyId}/contacts`)
|
|
const data = await response.json()
|
|
setContacts(data.contacts || [])
|
|
} catch (error) {
|
|
console.error('获取联系人失败:', error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (companyId) {
|
|
fetchContacts()
|
|
}
|
|
}, [companyId, companyType])
|
|
|
|
const columns: ColumnsType<Contact> = [
|
|
{
|
|
title: t('contact.name'),
|
|
dataIndex: 'name',
|
|
key: 'name',
|
|
render: (text, record) => (
|
|
<div>
|
|
<div style={{ fontWeight: 'bold' }}>{text}</div>
|
|
{record.position && <div style={{ fontSize: '12px', color: '#666' }}>{record.position}</div>}
|
|
</div>
|
|
)
|
|
},
|
|
{
|
|
title: t('contact.contactInfo'),
|
|
key: 'contact',
|
|
render: (_, record) => (
|
|
<Space direction="vertical" size={2}>
|
|
{record.mobile && <div><PhoneOutlined style={{ marginRight: 4 }} />{record.mobile}</div>}
|
|
{record.phone && <div style={{ fontSize: '12px', color: '#666' }}>{t('component.phonePrefix')}{record.phone}</div>}
|
|
{record.wechat && <div style={{ fontSize: '12px', color: '#666' }}>{t('component.wechatPrefix')}{record.wechat}</div>}
|
|
</Space>
|
|
)
|
|
},
|
|
{
|
|
title: t('contact.status'),
|
|
dataIndex: 'is_primary',
|
|
key: 'is_primary',
|
|
width: 100,
|
|
render: (isPrimary) => (
|
|
<Tag color={isPrimary ? 'green' : 'blue'}>
|
|
{isPrimary ? t('contact.primary') : t('contact.secondary')}
|
|
</Tag>
|
|
)
|
|
},
|
|
{
|
|
title: t('common.actions'),
|
|
key: 'actions',
|
|
width: 120,
|
|
render: (_, record) => (
|
|
<Space>
|
|
<Button type="text" icon={<EditOutlined />} onClick={() => handleEdit(record)} size="small" />
|
|
<Popconfirm title={t('common.confirmDelete')} onConfirm={() => handleDelete(record.id)} okText={t('common.yes')} cancelText={t('common.no')}>
|
|
<Button type="text" danger icon={<DeleteOutlined />} size="small" />
|
|
</Popconfirm>
|
|
</Space>
|
|
)
|
|
}
|
|
]
|
|
|
|
const handleSubmit = async (values: any) => {
|
|
try {
|
|
const url = editingContact ? `/api/${companyType}s/${companyId}/contacts/${editingContact.id}` : `/api/${companyType}s/${companyId}/contacts`
|
|
const method = editingContact ? 'PUT' : 'POST'
|
|
const response = await fetch(url, {
|
|
method,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ ...values, company_type: companyType, company_id: companyId })
|
|
})
|
|
if (response.ok) {
|
|
message.success(editingContact ? t('common.updateSuccess') : t('common.createSuccess'))
|
|
setModalVisible(false)
|
|
form.resetFields()
|
|
setEditingContact(null)
|
|
fetchContacts()
|
|
onContactsUpdated?.()
|
|
}
|
|
} catch (error) {
|
|
message.error(t('common.operationFailed'))
|
|
}
|
|
}
|
|
|
|
const handleEdit = (contact: Contact) => {
|
|
setEditingContact(contact)
|
|
form.setFieldsValue(contact)
|
|
setModalVisible(true)
|
|
}
|
|
|
|
const handleDelete = async (contactId: number) => {
|
|
try {
|
|
await fetch(`/api/${companyType}s/${companyId}/contacts/${contactId}`, { method: 'DELETE' })
|
|
message.success(t('common.deleteSuccess'))
|
|
fetchContacts()
|
|
onContactsUpdated?.()
|
|
} catch (error) {
|
|
message.error(t('common.deleteFailed'))
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
|
|
<div>
|
|
<h3>{t('contact.management')}</h3>
|
|
<p style={{ color: '#666' }}>{companyName} - {t(`company.${companyType}`)}</p>
|
|
</div>
|
|
<Button type="primary" icon={<PlusOutlined />} onClick={() => { setEditingContact(null); form.resetFields(); setModalVisible(true) }}>
|
|
{t('contact.addContact')}
|
|
</Button>
|
|
</div>
|
|
|
|
<Table columns={columns} dataSource={contacts} rowKey="id" loading={loading} pagination={{ pageSize: 10 }} size="middle" />
|
|
|
|
<Modal
|
|
title={editingContact ? t('contact.editContact') : t('contact.addContact')}
|
|
open={modalVisible}
|
|
onCancel={() => { setModalVisible(false); form.resetFields(); setEditingContact(null) }}
|
|
onOk={() => form.submit()}
|
|
width={600}
|
|
destroyOnClose
|
|
>
|
|
<Form form={form} layout="vertical" onFinish={handleSubmit} initialValues={{ is_primary: false }}>
|
|
<Form.Item name="name" label={t('contact.name')} rules={[{ required: true }]}>
|
|
<Input placeholder={t('contact.namePlaceholder')} />
|
|
</Form.Item>
|
|
<Form.Item name="position" label={t('contact.position')}>
|
|
<Input placeholder={t('contact.positionPlaceholder')} />
|
|
</Form.Item>
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
|
|
<Form.Item name="phone" label={t('contact.phone')}>
|
|
<Input placeholder={t('contact.phonePlaceholder')} />
|
|
</Form.Item>
|
|
<Form.Item name="mobile" label={t('contact.mobile')}>
|
|
<Input placeholder={t('contact.mobilePlaceholder')} />
|
|
</Form.Item>
|
|
</div>
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
|
|
<Form.Item name="wechat" label={t('contact.wechat')}>
|
|
<Input placeholder={t('contact.wechatPlaceholder')} />
|
|
</Form.Item>
|
|
<Form.Item name="line_id" label={t('contact.lineId')}>
|
|
<Input placeholder={t('contact.lineIdPlaceholder')} />
|
|
</Form.Item>
|
|
</div>
|
|
<Form.Item name="whatsapp" label="WhatsApp">
|
|
<Input placeholder={t('component.whatsappPlaceholder')} />
|
|
</Form.Item>
|
|
<Form.Item name="is_primary" label={t('contact.primaryContact')} valuePropName="checked">
|
|
<Switch />
|
|
</Form.Item>
|
|
<Form.Item name="notes" label={t('contact.notes')}>
|
|
<Input.TextArea rows={3} placeholder={t('contact.notesPlaceholder')} />
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default ContactManager
|