erp-platform/ui/src/views/crm/components/CustomerFormNew.tsx
2025-11-03 23:31:28 +03:00

518 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect, useCallback } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import {
FaSave,
FaTimes,
FaBuilding,
FaUser,
FaMapMarkerAlt,
FaCreditCard,
FaEnvelope,
} from 'react-icons/fa'
import LoadingSpinner from '../../../components/common/LoadingSpinner'
import {
BusinessParty,
BusinessPartyIndustryEnum,
BusinessPartyStatusEnum,
PaymentTerms,
} from '../../../types/common'
import { mockBusinessPartyNew } from '../../../mocks/mockBusinessParties'
import { Container } from '@/components/shared'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { mockCurrencies } from '@/mocks/mockCurrencies'
import { CustomerSegmentEnum, CustomerTypeEnum } from '@/types/crm'
import {
getBusinessPartyIndustryText,
getBusinessPartyStatusText,
getCustomerSegmentText,
getCustomerTypeText,
getPaymentTermsText,
} from '@/utils/erp'
interface ValidationErrors {
[key: string]: string
}
const CustomerForm: React.FC = () => {
const navigate = useNavigate()
const { id } = useParams<{ id: string }>()
const isEdit = Boolean(id)
const [loading, setLoading] = useState(false)
const [saving, setSaving] = useState(false)
const [errors, setErrors] = useState<ValidationErrors>({})
const [formData, setFormData] = useState<BusinessParty>(mockBusinessPartyNew)
const loadFormData = useCallback(async () => {
setLoading(true)
try {
if (isEdit && id) {
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000))
setFormData(mockBusinessPartyNew)
}
} catch (error) {
console.error('Error loading form data:', error)
} finally {
setLoading(false)
}
}, [isEdit, id])
useEffect(() => {
loadFormData()
}, [loadFormData])
const validateForm = (): boolean => {
const newErrors: ValidationErrors = {}
if (!formData.code.trim()) {
newErrors.code = 'Müşteri kodu zorunludur'
}
if (!formData.name.trim()) {
newErrors.name = 'Şirket adı zorunludur'
}
if (formData.creditLimit < 0) {
newErrors.creditLimit = "Kredi limiti 0'dan küçük olamaz"
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleInputChange = (field: keyof BusinessParty, value: string | number | boolean) => {
setFormData((prev) => ({
...prev,
[field]: value,
}))
// Clear error when user starts typing
if (errors[field]) {
setErrors((prev) => ({
...prev,
[field]: '',
}))
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!validateForm()) {
return
}
setSaving(true)
try {
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 2000))
console.log('Customer data:', {
...formData,
id: isEdit ? id : undefined,
})
// Show success message
alert(isEdit ? 'Müşteri başarıyla güncellendi!' : 'Müşteri başarıyla oluşturuldu!')
// Navigate back to list
navigate(ROUTES_ENUM.protected.crm.customers)
} catch (error) {
console.error('Error saving customer:', error)
alert('Bir hata oluştu. Lütfen tekrar deneyin.')
} finally {
setSaving(false)
}
}
const handleCancel = () => {
navigate(ROUTES_ENUM.protected.crm.customers)
}
if (loading) {
return <LoadingSpinner />
}
return (
<Container>
<div className="space-y-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-lg font-semibold text-gray-900">
{isEdit ? 'Müşteri Düzenle' : 'Yeni Müşteri'}
</h2>
<p className="text-gray-600">
{isEdit ? 'Mevcut müşteri bilgilerini güncelleyin' : 'Yeni müşteri bilgilerini girin'}
</p>
</div>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="space-y-3">
{/* General Information */}
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-3 border-b border-gray-200">
<h3 className="text-base font-medium text-gray-900 flex items-center">
<FaBuilding className="w-5 h-5 mr-2" />
Genel Bilgiler
</h3>
</div>
<div className="p-4 space-y-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Müşteri Kodu *
</label>
<input
type="text"
value={formData.code}
onChange={(e) => handleInputChange('code', e.target.value)}
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.code
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="Örn: MST001"
/>
{errors.customerCode && (
<p className="mt-1 text-sm text-red-600">{errors.customerCode}</p>
)}
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Müşteri Tipi
</label>
<select
value={formData.customerType}
onChange={(e) => handleInputChange('customerType', e.target.value)}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
>
{Object.values(CustomerTypeEnum).map((type) => (
<option key={type} value={type}>
{getCustomerTypeText(type)}
</option>
))}
</select>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Şirket Adı *
</label>
<input
type="text"
value={formData.name}
onChange={(e) => handleInputChange('name', e.target.value)}
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.name
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="Şirket adını girin"
/>
{errors.companyName && (
<p className="mt-1 text-sm text-red-600">{errors.companyName}</p>
)}
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Sektör</label>
<select
value={formData.industry}
onChange={(e) => handleInputChange('industry', e.target.value)}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
>
<option value="">Sektör seçin</option>
{Object.values(BusinessPartyIndustryEnum).map((industry) => (
<option key={industry} value={industry}>
{getBusinessPartyIndustryText(industry)}
</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Website</label>
<input
type="url"
value={formData.website}
onChange={(e) => handleInputChange('website', e.target.value)}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="https://www.ornek.com"
/>
</div>
</div>
</div>
{/* Contact Information */}
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-3 border-b border-gray-200">
<h3 className="text-base font-medium text-gray-900 flex items-center">
<FaUser className="w-5 h-5 mr-2" />
İletişim Bilgileri
</h3>
</div>
<div className="p-4 space-y-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Email *</label>
<input
type="email"
value={formData.email}
onChange={(e) => handleInputChange('email', e.target.value)}
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.email
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="email@ornek.com"
/>
{errors.email && <p className="mt-1 text-sm text-red-600">{errors.email}</p>}
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Telefon *</label>
<input
type="tel"
value={formData.phoneNumber}
onChange={(e) => handleInputChange('phoneNumber', e.target.value)}
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.phoneNumber
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="+90 212 555 0123"
/>
{errors.phoneNumber && <p className="mt-1 text-sm text-red-600">{errors.phoneNumber}</p>}
</div>
</div>
</div>
</div>
{/* Address Information */}
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-3 border-b border-gray-200">
<h3 className="text-base font-medium text-gray-900 flex items-center">
<FaMapMarkerAlt className="w-5 h-5 mr-2" />
Adres Bilgileri
</h3>
</div>
</div>
{/* Business Information */}
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-3 border-b border-gray-200">
<h3 className="text-base font-medium text-gray-900 flex items-center">
<FaCreditCard className="w-5 h-5 mr-2" />
İş Bilgileri
</h3>
</div>
<div className="p-4 space-y-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Vergi Numarası
</label>
<input
type="text"
value={formData.taxNumber}
onChange={(e) => handleInputChange('taxNumber', e.target.value)}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="1234567890"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Ticaret Sicil No
</label>
<input
type="text"
value={formData.registrationNumber}
onChange={(e) => handleInputChange('registrationNumber', e.target.value)}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="98765"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Kredi Limiti
</label>
<input
type="number"
min="0"
step="0.01"
value={formData.creditLimit}
onChange={(e) =>
handleInputChange('creditLimit', parseFloat(e.target.value) || 0)
}
className={`block w-full px-3 py-1.5 text-sm border rounded-md shadow-sm focus:outline-none focus:ring-1 ${
errors.creditLimit
? 'border-red-300 focus:border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500'
}`}
placeholder="0.00"
/>
{errors.creditLimit && (
<p className="mt-1 text-sm text-red-600">{errors.creditLimit}</p>
)}
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Ödeme Koşulları
</label>
<select
value={formData.paymentTerms}
onChange={(e) => handleInputChange('paymentTerms', e.target.value)}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
>
{Object.values(PaymentTerms).map((term) => (
<option key={term} value={term}>
{getPaymentTermsText(term)}
</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Para Birimi
</label>
<select
value={formData.currency}
onChange={(e) => handleInputChange('currency', e.target.value)}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
>
{mockCurrencies.map((currency) => (
<option key={currency.value} value={currency.value}>
{currency.value} - {currency.label}
</option>
))}
</select>
</div>
</div>
</div>
</div>
{/* Relationship Information */}
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-3 border-b border-gray-200">
<h3 className="text-base font-medium text-gray-900 flex items-center">
<FaEnvelope className="w-5 h-5 mr-2" />
İlişki Yönetimi
</h3>
</div>
<div className="p-4 space-y-3">
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Müşteri Durumu
</label>
<select
value={formData.status}
onChange={(e) => handleInputChange('status', e.target.value)}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
>
{Object.values(BusinessPartyStatusEnum).map((status) => (
<option key={status} value={status}>
{getBusinessPartyStatusText(status)}
</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Müşteri Segmenti
</label>
<select
value={formData.customerSegment}
onChange={(e) => handleInputChange('customerSegment', e.target.value)}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
>
{Object.values(CustomerSegmentEnum).map((segment) => (
<option key={segment} value={segment}>
{getCustomerSegmentText(segment)}
</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Sorumlu Satış Temsilcisi
</label>
<input
type="text"
value={formData.assignedSalesRep}
onChange={(e) => handleInputChange('assignedSalesRep', e.target.value)}
className="block w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:border-blue-500 focus:ring-blue-500"
placeholder="Satış temsilcisi adı"
/>
</div>
</div>
<div className="flex items-center">
<input
type="checkbox"
id="isActive"
checked={formData.isActive}
onChange={(e) => handleInputChange('isActive', e.target.checked)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label htmlFor="isActive" className="ml-2 block text-sm text-gray-900">
Aktif
</label>
</div>
</div>
</div>
{/* Form Actions */}
<div className="flex items-center justify-end space-x-2 bg-gray-50 px-4 py-3 rounded-b-lg">
<button
type="button"
onClick={handleCancel}
className="inline-flex items-center px-4 py-1.5 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<FaTimes className="w-4 h-4 mr-2" />
İptal
</button>
<button
type="submit"
disabled={saving}
className="inline-flex items-center px-4 py-1.5 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
{saving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
Kaydediliyor...
</>
) : (
<>
<FaSave className="w-4 h-4 mr-2" />
{isEdit ? 'Güncelle' : 'Kaydet'}
</>
)}
</button>
</div>
</form>
</div>
</Container>
)
}
export default CustomerForm