erp-platform/ui/src/views/accounting/components/CurrentAccountForm.tsx

425 lines
16 KiB
TypeScript
Raw Normal View History

2025-09-17 09:46:58 +00:00
import React, { useState, useEffect } from 'react'
import { FaTimes, FaUser, FaBuilding, FaCreditCard, FaPhoneAlt } from 'react-icons/fa'
import { FiCurrentAccount, AccountTypeEnum, RiskGroupEnum } from '../../../types/fi'
import { mockBusinessParties } from '../../../mocks/mockBusinessParties'
import { getAccountTypeText, getRiskGroupText } from '../../../utils/erp'
import { mockCurrencies } from '@/mocks/mockCurrencies'
2025-09-15 09:31:47 +00:00
interface CurrentAccountFormProps {
2025-09-17 09:46:58 +00:00
account?: FiCurrentAccount
isOpen: boolean
onClose: () => void
onSave: (account: Partial<FiCurrentAccount>) => void
2025-09-15 09:31:47 +00:00
}
const CurrentAccountForm: React.FC<CurrentAccountFormProps> = ({
account,
isOpen,
onClose,
onSave,
}) => {
const [formData, setFormData] = useState<
Partial<FiCurrentAccount> & {
2025-09-17 09:46:58 +00:00
id?: string
2025-09-15 09:31:47 +00:00
}
>({
2025-09-17 09:46:58 +00:00
accountCode: '',
businessPartyId: '',
2025-09-15 09:31:47 +00:00
type: AccountTypeEnum.Customer,
2025-09-17 09:46:58 +00:00
contactPerson: '',
phone: '',
email: '',
address: '',
taxNumber: '',
taxOffice: '',
2025-09-15 09:31:47 +00:00
creditLimit: 0,
2025-09-17 09:46:58 +00:00
currency: 'TRY',
2025-09-15 09:31:47 +00:00
riskGroup: RiskGroupEnum.Low,
paymentTerm: 30,
isActive: true,
2025-09-17 09:46:58 +00:00
})
2025-09-15 09:31:47 +00:00
2025-09-17 09:46:58 +00:00
const [errors, setErrors] = useState<Record<string, string>>({})
2025-09-15 09:31:47 +00:00
useEffect(() => {
if (account) {
setFormData({
...account,
creditLimit: account.creditLimit || 0,
paymentTerm: account.paymentTerm || 30,
2025-09-17 09:46:58 +00:00
})
2025-09-15 09:31:47 +00:00
} else {
setFormData({
2025-09-17 09:46:58 +00:00
accountCode: '',
businessPartyId: '',
2025-09-15 09:31:47 +00:00
type: AccountTypeEnum.Customer,
2025-09-17 09:46:58 +00:00
contactPerson: '',
phone: '',
email: '',
address: '',
taxNumber: '',
taxOffice: '',
2025-09-15 09:31:47 +00:00
creditLimit: 0,
2025-09-17 09:46:58 +00:00
currency: 'TRY',
2025-09-15 09:31:47 +00:00
riskGroup: RiskGroupEnum.Low,
paymentTerm: 30,
isActive: true,
2025-09-17 09:46:58 +00:00
})
2025-09-15 09:31:47 +00:00
}
2025-09-17 09:46:58 +00:00
setErrors({})
}, [account, isOpen])
2025-09-15 09:31:47 +00:00
const handleInputChange = (
2025-09-17 09:46:58 +00:00
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
2025-09-15 09:31:47 +00:00
) => {
2025-09-17 09:46:58 +00:00
const { name, value, type } = e.target
let parsedValue: string | number | boolean = value
2025-09-15 09:31:47 +00:00
2025-09-17 09:46:58 +00:00
if (type === 'number') {
parsedValue = value === '' ? 0 : parseFloat(value)
} else if (type === 'checkbox') {
parsedValue = (e.target as HTMLInputElement).checked
2025-09-15 09:31:47 +00:00
}
setFormData((prev) => ({
...prev,
[name]: parsedValue,
2025-09-17 09:46:58 +00:00
}))
2025-09-15 09:31:47 +00:00
// Clear error when user starts typing
if (errors[name]) {
setErrors((prev) => ({
...prev,
2025-09-17 09:46:58 +00:00
[name]: '',
}))
2025-09-15 09:31:47 +00:00
}
2025-09-17 09:46:58 +00:00
}
2025-09-15 09:31:47 +00:00
const validateForm = () => {
2025-09-17 09:46:58 +00:00
const newErrors: Record<string, string> = {}
2025-09-15 09:31:47 +00:00
if (!formData.accountCode?.trim()) {
2025-09-17 09:46:58 +00:00
newErrors.accountCode = 'Hesap kodu zorunludur'
2025-09-15 09:31:47 +00:00
}
if (!formData.businessPartyId?.trim()) {
2025-09-17 09:46:58 +00:00
newErrors.businessPartyId = 'Ünvan zorunludur'
2025-09-15 09:31:47 +00:00
}
if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
2025-09-17 09:46:58 +00:00
newErrors.email = 'Geçerli bir e-posta adresi giriniz'
2025-09-15 09:31:47 +00:00
}
if (formData.creditLimit && formData.creditLimit < 0) {
2025-09-17 09:46:58 +00:00
newErrors.creditLimit = 'Kredi limiti negatif olamaz'
2025-09-15 09:31:47 +00:00
}
if (formData.paymentTerm && formData.paymentTerm < 0) {
2025-09-17 09:46:58 +00:00
newErrors.paymentTerm = 'Ödeme vadesi negatif olamaz'
2025-09-15 09:31:47 +00:00
}
2025-09-17 09:46:58 +00:00
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
2025-09-15 09:31:47 +00:00
const handleSubmit = (e: React.FormEvent) => {
2025-09-17 09:46:58 +00:00
e.preventDefault()
2025-09-15 09:31:47 +00:00
if (validateForm()) {
2025-09-17 09:46:58 +00:00
onSave(formData)
onClose()
2025-09-15 09:31:47 +00:00
}
2025-09-17 09:46:58 +00:00
}
2025-09-15 09:31:47 +00:00
2025-09-17 09:46:58 +00:00
if (!isOpen) return null
2025-09-15 09:31:47 +00:00
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg w-full max-w-3xl max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between p-4 border-b">
<h2 className="text-lg font-semibold text-gray-900">
2025-09-17 09:46:58 +00:00
{account ? 'Cari Hesap Düzenle' : 'Yeni Cari Hesap'}
2025-09-15 09:31:47 +00:00
</h2>
2025-09-17 09:46:58 +00:00
<button onClick={onClose} className="text-gray-400 hover:text-gray-600">
2025-09-15 09:31:47 +00:00
<FaTimes className="w-5 h-5" />
</button>
</div>
<form onSubmit={handleSubmit} className="p-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Temel Bilgiler */}
<div className="space-y-3">
<h3 className="text-base font-medium text-gray-900 flex items-center gap-2">
<FaUser className="w-4 h-4 text-blue-600" />
Temel Bilgiler
</h3>
<div>
2025-09-17 09:46:58 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Hesap Kodu *</label>
2025-09-15 09:31:47 +00:00
<input
type="text"
name="accountCode"
2025-09-17 09:46:58 +00:00
value={formData.accountCode || ''}
2025-09-15 09:31:47 +00:00
onChange={handleInputChange}
className={`w-full px-3 py-1.5 text-sm border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
2025-09-17 09:46:58 +00:00
errors.accountCode ? 'border-red-500' : 'border-gray-300'
2025-09-15 09:31:47 +00:00
}`}
placeholder="CA001"
/>
{errors.accountCode && (
2025-09-17 09:46:58 +00:00
<p className="text-red-500 text-xs mt-1">{errors.accountCode}</p>
2025-09-15 09:31:47 +00:00
)}
</div>
<div>
2025-09-17 09:46:58 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Ünvan *</label>
2025-09-15 09:31:47 +00:00
<select
name="businessPartyId"
2025-09-17 09:46:58 +00:00
value={formData.businessPartyId || ''}
2025-09-15 09:31:47 +00:00
onChange={handleInputChange}
className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
required
>
<option value="">Tedarikçi Seçiniz</option>
{mockBusinessParties.map((businessParty) => (
<option key={businessParty.id} value={businessParty.id}>
{businessParty.name} ({businessParty.code})
</option>
))}
</select>
2025-09-17 09:46:58 +00:00
{errors.title && <p className="text-red-500 text-xs mt-1">{errors.title}</p>}
2025-09-15 09:31:47 +00:00
</div>
<div>
2025-09-17 09:46:58 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Hesap Türü</label>
2025-09-15 09:31:47 +00:00
<select
name="type"
value={formData.type}
onChange={handleInputChange}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{Object.values(AccountTypeEnum).map((type) => (
<option key={type} value={type}>
{getAccountTypeText(type)}
</option>
))}
</select>
</div>
<div>
2025-09-17 09:46:58 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Yetkili Kişi</label>
2025-09-15 09:31:47 +00:00
<input
type="text"
name="contactPerson"
2025-09-17 09:46:58 +00:00
value={formData.contactPerson || ''}
2025-09-15 09:31:47 +00:00
onChange={handleInputChange}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Yetkili kişi adı"
/>
</div>
</div>
{/* İletişim Bilgileri */}
<div className="space-y-3">
<h3 className="text-base font-medium text-gray-900 flex items-center gap-2">
<FaPhoneAlt className="w-4 h-4 text-green-600" />
İletişim Bilgileri
</h3>
<div>
2025-09-17 09:46:58 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Telefon</label>
2025-09-15 09:31:47 +00:00
<input
type="text"
name="phone"
2025-09-17 09:46:58 +00:00
value={formData.phone || ''}
2025-09-15 09:31:47 +00:00
onChange={handleInputChange}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="+90 212 555 1234"
/>
</div>
<div>
2025-09-17 09:46:58 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">E-posta</label>
2025-09-15 09:31:47 +00:00
<input
type="email"
name="email"
2025-09-17 09:46:58 +00:00
value={formData.email || ''}
2025-09-15 09:31:47 +00:00
onChange={handleInputChange}
className={`w-full px-3 py-1.5 text-sm border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
2025-09-17 09:46:58 +00:00
errors.email ? 'border-red-500' : 'border-gray-300'
2025-09-15 09:31:47 +00:00
}`}
placeholder="info@company.com"
/>
2025-09-17 09:46:58 +00:00
{errors.email && <p className="text-red-500 text-xs mt-1">{errors.email}</p>}
2025-09-15 09:31:47 +00:00
</div>
<div>
2025-09-17 09:46:58 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Adres</label>
2025-09-15 09:31:47 +00:00
<textarea
name="address"
2025-09-17 09:46:58 +00:00
value={formData.address || ''}
2025-09-15 09:31:47 +00:00
onChange={handleInputChange}
rows={3}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Tam adres"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Vergi Numarası
</label>
<input
type="text"
name="taxNumber"
2025-09-17 09:46:58 +00:00
value={formData.taxNumber || ''}
2025-09-15 09:31:47 +00:00
onChange={handleInputChange}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="1234567890"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Vergi Dairesi
</label>
<input
type="text"
name="taxOffice"
2025-09-17 09:46:58 +00:00
value={formData.taxOffice || ''}
2025-09-15 09:31:47 +00:00
onChange={handleInputChange}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Beylikdüzü V.D."
/>
</div>
</div>
</div>
{/* Mali Bilgiler */}
<div className="space-y-3">
<h3 className="text-base font-medium text-gray-900 flex items-center gap-2">
<FaCreditCard className="w-4 h-4 text-purple-600" />
Mali Bilgiler
</h3>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Kredi Limiti
</label>
<input
type="number"
name="creditLimit"
value={formData.creditLimit || 0}
onChange={handleInputChange}
min="0"
step="0.01"
className={`w-full px-3 py-1.5 text-sm border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
2025-09-17 09:46:58 +00:00
errors.creditLimit ? 'border-red-500' : 'border-gray-300'
2025-09-15 09:31:47 +00:00
}`}
placeholder="0.00"
/>
{errors.creditLimit && (
2025-09-17 09:46:58 +00:00
<p className="text-red-500 text-xs mt-1">{errors.creditLimit}</p>
2025-09-15 09:31:47 +00:00
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Para Birimi
</label>
<select
name="currency"
value={formData.currency}
onChange={handleInputChange}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
2025-09-17 09:46:58 +00:00
{mockCurrencies.map((currency) => (
<option key={currency.value} value={currency.value}>
{currency.value} - {currency.label}
</option>
))}
2025-09-15 09:31:47 +00:00
</select>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
2025-09-17 09:46:58 +00:00
<label className="block text-sm font-medium text-gray-700 mb-1">Risk Grubu</label>
2025-09-15 09:31:47 +00:00
<select
name="riskGroup"
value={formData.riskGroup}
onChange={handleInputChange}
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{Object.values(RiskGroupEnum).map((risk) => (
<option key={risk} value={risk}>
{getRiskGroupText(risk)}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Ödeme Vadesi (Gün)
</label>
<input
type="number"
name="paymentTerm"
value={formData.paymentTerm || 30}
onChange={handleInputChange}
min="0"
className={`w-full px-3 py-1.5 text-sm border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
2025-09-17 09:46:58 +00:00
errors.paymentTerm ? 'border-red-500' : 'border-gray-300'
2025-09-15 09:31:47 +00:00
}`}
placeholder="30"
/>
{errors.paymentTerm && (
2025-09-17 09:46:58 +00:00
<p className="text-red-500 text-xs mt-1">{errors.paymentTerm}</p>
2025-09-15 09:31:47 +00:00
)}
</div>
</div>
</div>
{/* Durum */}
<div className="space-y-3">
<h3 className="text-base font-medium text-gray-900 flex items-center gap-2">
<FaBuilding className="w-4 h-4 text-orange-600" />
Durum
</h3>
<div className="flex items-center">
<input
type="checkbox"
name="isActive"
checked={formData.isActive || false}
onChange={handleInputChange}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
2025-09-17 09:46:58 +00:00
<label className="ml-2 block text-sm text-gray-700">Aktif hesap</label>
2025-09-15 09:31:47 +00:00
</div>
</div>
</div>
{/* Form Actions */}
<div className="flex justify-end gap-3 mt-6 pt-4 border-t">
<button
type="button"
onClick={onClose}
className="px-4 py-1.5 text-sm text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 transition-colors"
>
İptal
</button>
<button
type="submit"
className="px-4 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
2025-09-17 09:46:58 +00:00
{account ? 'Güncelle' : 'Ekle'}
2025-09-15 09:31:47 +00:00
</button>
</div>
</form>
</div>
</div>
2025-09-17 09:46:58 +00:00
)
}
2025-09-15 09:31:47 +00:00
2025-09-17 09:46:58 +00:00
export default CurrentAccountForm