Initial commit: ERP system with advance verification fixes
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
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' }}>电话: {record.phone}</div>}
|
||||
{record.wechat && <div style={{ fontSize: '12px', color: '#666' }}>微信: {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="输入WhatsApp号码" />
|
||||
</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
|
||||
Reference in New Issue
Block a user