Files
leocrm/frontend/src/components/contacts/ContactEditModal.tsx
T

222 lines
10 KiB
TypeScript
Raw Normal View History

import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Modal } from '@/components/ui/Modal';
import { Input } from '@/components/ui/Input';
import { Select } from '@/components/ui/Select';
import { Button } from '@/components/ui/Button';
import { useToast } from '@/components/ui/Toast';
import {
type UnifiedContact,
useCreateUnifiedContact,
useUpdateUnifiedContact,
} from '@/api/hooks';
export interface ContactEditModalProps {
open: boolean;
onClose: () => void;
contact?: UnifiedContact | null;
onSaved?: (id: string) => void;
}
export function ContactEditModal({ open, onClose, contact, onSaved }: ContactEditModalProps) {
const { t } = useTranslation();
const toast = useToast();
const createMutation = useCreateUnifiedContact();
const updateMutation = useUpdateUnifiedContact();
const isEdit = !!contact;
const [type, setType] = useState<'company' | 'person'>('company');
const [name, setName] = useState('');
const [firstname, setFirstname] = useState('');
const [surname, setSurname] = useState('');
const [code, setCode] = useState('');
const [email1, setEmail1] = useState('');
const [email2, setEmail2] = useState('');
const [phone1, setPhone1] = useState('');
const [phone2, setPhone2] = useState('');
const [website, setWebsite] = useState('');
const [mailingStreet, setMailingStreet] = useState('');
const [mailingNumber, setMailingNumber] = useState('');
const [mailingPostalcode, setMailingPostalcode] = useState('');
const [mailingCity, setMailingCity] = useState('');
const [mailingCountry, setMailingCountry] = useState('');
const [vatCode, setVatCode] = useState('');
const [fiscalCode, setFiscalCode] = useState('');
const [commerceCode, setCommerceCode] = useState('');
const [bic, setBic] = useState('');
const [bankAccount, setBankAccount] = useState('');
const [tags, setTags] = useState('');
const [projectnote, setProjectnote] = useState('');
const [contactWarning, setContactWarning] = useState('');
useEffect(() => {
if (open) {
setType((contact?.type as 'company' | 'person') || 'company');
setName(contact?.name || '');
setFirstname(contact?.firstname || '');
setSurname(contact?.surname || '');
setCode(contact?.code || '');
setEmail1(contact?.email_1 || '');
setEmail2(contact?.email_2 || '');
setPhone1(contact?.phone_1 || '');
setPhone2(contact?.phone_2 || '');
setWebsite(contact?.website || '');
setMailingStreet(contact?.mailing_street || '');
setMailingNumber(contact?.mailing_number || '');
setMailingPostalcode(contact?.mailing_postalcode || '');
setMailingCity(contact?.mailing_city || '');
setMailingCountry(contact?.mailing_country || '');
setVatCode(contact?.vat_code || '');
setFiscalCode(contact?.fiscal_code || '');
setCommerceCode(contact?.commerce_code || '');
setBic(contact?.bic || '');
setBankAccount(contact?.bank_account || '');
setTags(contact?.tags || '');
setProjectnote(contact?.projectnote || '');
setContactWarning(contact?.contact_warning || '');
}
}, [open, contact]);
const handleSave = async () => {
const data: Partial<UnifiedContact> = {
type,
name: type === 'company' ? name : null,
firstname: type === 'person' ? firstname : null,
surname: type === 'person' ? surname : null,
code: code || null,
email_1: email1 || null,
email_2: email2 || null,
phone_1: phone1 || null,
phone_2: phone2 || null,
website: website || null,
mailing_street: mailingStreet || null,
mailing_number: mailingNumber || null,
mailing_postalcode: mailingPostalcode || null,
mailing_city: mailingCity || null,
mailing_country: mailingCountry || null,
vat_code: vatCode || null,
fiscal_code: fiscalCode || null,
commerce_code: commerceCode || null,
bic: bic || null,
bank_account: bankAccount || null,
tags: tags || null,
projectnote: projectnote || null,
contact_warning: contactWarning || null,
};
try {
if (isEdit && contact) {
await updateMutation.mutateAsync({ id: contact.id, data });
toast.success(t('contacts.updated'));
onSaved?.(contact.id);
} else {
const result = await createMutation.mutateAsync(data) as { id: string };
toast.success(t('contacts.created'));
onSaved?.(result.id);
}
onClose();
} catch (err: any) {
toast.error(err.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed')));
}
};
return (
<Modal open={open} onClose={onClose} title={isEdit ? t('contacts.edit') : t('contacts.create')} size="xl" fullScreenMobile>
<div className="space-y-4">
{/* Type */}
<Select
label={t('contacts.type')}
value={type}
onChange={(e) => setType(e.target.value as 'company' | 'person')}
options={[
{ value: 'company', label: t('contacts.companies') },
{ value: 'person', label: t('contacts.persons') },
]}
data-testid="contact-type-select"
/>
{/* Name fields */}
{type === 'company' ? (
<Input label={t('contacts.name')} value={name} onChange={(e) => setName(e.target.value)} required data-testid="contact-name-input" placeholder="TechCorp GmbH" />
) : (
<div className="grid grid-cols-2 gap-3">
<Input label={t('contacts.firstName')} value={firstname} onChange={(e) => setFirstname(e.target.value)} data-testid="contact-first-name-input" />
<Input label={t('contacts.lastName')} value={surname} onChange={(e) => setSurname(e.target.value)} data-testid="contact-last-name-input" />
</div>
)}
{/* Code */}
<Input label={t('contacts.code')} value={code} onChange={(e) => setCode(e.target.value)} placeholder="K-00123" />
{/* Communication */}
<div className="border border-secondary-200 rounded-lg p-3">
<h3 className="text-sm font-semibold text-secondary-700 mb-2">{t('contacts.communication')}</h3>
<div className="grid grid-cols-2 gap-3">
<Input label={t('contacts.email') + ' 1'} type="email" value={email1} onChange={(e) => setEmail1(e.target.value)} />
<Input label={t('contacts.email') + ' 2'} type="email" value={email2} onChange={(e) => setEmail2(e.target.value)} />
<Input label={t('contacts.phone') + ' 1'} value={phone1} onChange={(e) => setPhone1(e.target.value)} />
<Input label={t('contacts.phone') + ' 2'} value={phone2} onChange={(e) => setPhone2(e.target.value)} />
<Input label={t('contacts.website')} value={website} onChange={(e) => setWebsite(e.target.value)} />
</div>
</div>
{/* Mailing Address */}
<div className="border border-secondary-200 rounded-lg p-3">
<h3 className="text-sm font-semibold text-secondary-700 mb-2">{t('contacts.mailingAddress')}</h3>
<div className="grid grid-cols-2 gap-3">
<Input label={t('address.street')} value={mailingStreet} onChange={(e) => setMailingStreet(e.target.value)} />
<Input label={t('address.streetNumber')} value={mailingNumber} onChange={(e) => setMailingNumber(e.target.value)} />
<Input label={t('address.zip')} value={mailingPostalcode} onChange={(e) => setMailingPostalcode(e.target.value)} />
<Input label={t('address.city')} value={mailingCity} onChange={(e) => setMailingCity(e.target.value)} />
<Input label={t('address.country')} value={mailingCountry} onChange={(e) => setMailingCountry(e.target.value)} />
</div>
</div>
{/* Financial */}
<div className="border border-secondary-200 rounded-lg p-3">
<h3 className="text-sm font-semibold text-secondary-700 mb-2">{t('contacts.financial')}</h3>
<div className="grid grid-cols-2 gap-3">
<Input label={t('contacts.vatCode')} value={vatCode} onChange={(e) => setVatCode(e.target.value)} />
<Input label={t('contacts.fiscalCode')} value={fiscalCode} onChange={(e) => setFiscalCode(e.target.value)} />
<Input label={t('contacts.commerceCode')} value={commerceCode} onChange={(e) => setCommerceCode(e.target.value)} />
<Input label={t('contacts.bic')} value={bic} onChange={(e) => setBic(e.target.value)} />
<Input label={t('contacts.bankAccount')} value={bankAccount} onChange={(e) => setBankAccount(e.target.value)} />
</div>
</div>
{/* Notes */}
<div className="border border-secondary-200 rounded-lg p-3">
<h3 className="text-sm font-semibold text-secondary-700 mb-2">{t('contacts.notes')}</h3>
<div className="space-y-3">
<Input label={t('contacts.tags')} value={tags} onChange={(e) => setTags(e.target.value)} placeholder="tag1, tag2" />
<div>
<label className="block text-sm font-medium text-secondary-700 mb-1">{t('contacts.projectnote')}</label>
<textarea
value={projectnote}
onChange={(e) => setProjectnote(e.target.value)}
className="w-full px-3 py-2 text-sm rounded-md border border-secondary-300 focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-20"
/>
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 mb-1">{t('contacts.contactWarning')}</label>
<textarea
value={contactWarning}
onChange={(e) => setContactWarning(e.target.value)}
className="w-full px-3 py-2 text-sm rounded-md border border-secondary-300 focus:outline-none focus:ring-2 focus:ring-primary-500 min-h-20"
/>
</div>
</div>
</div>
{/* Actions */}
<div className="flex justify-end gap-2 pt-2">
<Button variant="secondary" onClick={onClose}>{t('common.cancel')}</Button>
<Button onClick={handleSave} isLoading={createMutation.isPending || updateMutation.isPending} data-testid="contact-submit-btn">
{isEdit ? t('common.save') : t('common.create')}
</Button>
</div>
</div>
</Modal>
);
}