561 lines
20 KiB
TypeScript
561 lines
20 KiB
TypeScript
|
|
import React, { useState, useEffect } from "react";
|
|||
|
|
import { FaSave, FaTimes, FaPlus, FaTrash, FaFileAlt } from "react-icons/fa";
|
|||
|
|
import {
|
|||
|
|
FiInvoice,
|
|||
|
|
FiInvoiceItem,
|
|||
|
|
InvoiceTypeEnum,
|
|||
|
|
InvoiceStatusEnum,
|
|||
|
|
PaymentStatusEnum,
|
|||
|
|
} from "../../../types/fi";
|
|||
|
|
import { mockCurrentAccounts } from "../../../mocks/mockCurrentAccounts";
|
|||
|
|
|
|||
|
|
interface InvoiceFormProps {
|
|||
|
|
invoice?: FiInvoice;
|
|||
|
|
onSave: (invoice: Partial<FiInvoice>) => void;
|
|||
|
|
onCancel: () => void;
|
|||
|
|
isVisible: boolean;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const InvoiceForm: React.FC<InvoiceFormProps> = ({
|
|||
|
|
invoice,
|
|||
|
|
onSave,
|
|||
|
|
onCancel,
|
|||
|
|
isVisible,
|
|||
|
|
}) => {
|
|||
|
|
const [formData, setFormData] = useState<Partial<FiInvoice>>({
|
|||
|
|
invoiceNumber: "",
|
|||
|
|
invoiceType: InvoiceTypeEnum.Sales,
|
|||
|
|
currentAccountId: "",
|
|||
|
|
invoiceDate: new Date(),
|
|||
|
|
dueDate: new Date(),
|
|||
|
|
deliveryDate: new Date(),
|
|||
|
|
subtotal: 0,
|
|||
|
|
taxAmount: 0,
|
|||
|
|
discountAmount: 0,
|
|||
|
|
totalAmount: 0,
|
|||
|
|
paidAmount: 0,
|
|||
|
|
remainingAmount: 0,
|
|||
|
|
currency: "TRY",
|
|||
|
|
status: InvoiceStatusEnum.Draft,
|
|||
|
|
paymentStatus: PaymentStatusEnum.Unpaid,
|
|||
|
|
items: [],
|
|||
|
|
waybillNumber: "",
|
|||
|
|
notes: "",
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const [newItem, setNewItem] = useState<Partial<FiInvoiceItem>>({
|
|||
|
|
description: "",
|
|||
|
|
quantity: 1,
|
|||
|
|
unitPrice: 0,
|
|||
|
|
taxRate: 18,
|
|||
|
|
discountRate: 0,
|
|||
|
|
unit: "Adet",
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (invoice) {
|
|||
|
|
setFormData({
|
|||
|
|
...invoice,
|
|||
|
|
invoiceDate: new Date(invoice.invoiceDate),
|
|||
|
|
dueDate: new Date(invoice.dueDate),
|
|||
|
|
deliveryDate: invoice.deliveryDate
|
|||
|
|
? new Date(invoice.deliveryDate)
|
|||
|
|
: new Date(),
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
// Generate new invoice number
|
|||
|
|
const now = new Date();
|
|||
|
|
const year = now.getFullYear();
|
|||
|
|
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|||
|
|
const invoiceNumber = `FT${year}${month}${String(
|
|||
|
|
Math.floor(Math.random() * 10000)
|
|||
|
|
).padStart(4, "0")}`;
|
|||
|
|
|
|||
|
|
setFormData((prev) => ({
|
|||
|
|
...prev,
|
|||
|
|
invoiceNumber,
|
|||
|
|
invoiceDate: now,
|
|||
|
|
dueDate: new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000), // 30 days
|
|||
|
|
deliveryDate: now,
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
}, [invoice]);
|
|||
|
|
|
|||
|
|
const calculateItemTotal = (item: Partial<FiInvoiceItem>) => {
|
|||
|
|
const subtotal = (item.quantity || 0) * (item.unitPrice || 0);
|
|||
|
|
const discountAmount = subtotal * ((item.discountRate || 0) / 100);
|
|||
|
|
const taxableAmount = subtotal - discountAmount;
|
|||
|
|
const taxAmount = taxableAmount * ((item.taxRate || 0) / 100);
|
|||
|
|
return taxableAmount + taxAmount;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const calculateInvoiceTotals = (items: FiInvoiceItem[]) => {
|
|||
|
|
const subtotal = items.reduce(
|
|||
|
|
(sum, item) => sum + item.quantity * item.unitPrice,
|
|||
|
|
0
|
|||
|
|
);
|
|||
|
|
const discountAmount = items.reduce(
|
|||
|
|
(sum, item) => sum + item.discountAmount,
|
|||
|
|
0
|
|||
|
|
);
|
|||
|
|
const taxAmount = items.reduce((sum, item) => sum + item.taxAmount, 0);
|
|||
|
|
const totalAmount = items.reduce((sum, item) => sum + item.lineTotal, 0);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
subtotal,
|
|||
|
|
discountAmount,
|
|||
|
|
taxAmount,
|
|||
|
|
totalAmount,
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleAddItem = () => {
|
|||
|
|
if (!newItem.description || !newItem.quantity || !newItem.unitPrice) {
|
|||
|
|
alert("Lütfen ürün bilgilerini doldurun");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const item: FiInvoiceItem = {
|
|||
|
|
id: Date.now().toString(),
|
|||
|
|
invoiceId: formData.id || "",
|
|||
|
|
description: newItem.description!,
|
|||
|
|
quantity: newItem.quantity!,
|
|||
|
|
unitPrice: newItem.unitPrice!,
|
|||
|
|
unit: newItem.unit || "Adet",
|
|||
|
|
taxRate: newItem.taxRate || 18,
|
|||
|
|
discountRate: newItem.discountRate || 0,
|
|||
|
|
lineTotal: calculateItemTotal(newItem),
|
|||
|
|
discountAmount:
|
|||
|
|
(newItem.quantity || 0) *
|
|||
|
|
(newItem.unitPrice || 0) *
|
|||
|
|
((newItem.discountRate || 0) / 100),
|
|||
|
|
taxAmount:
|
|||
|
|
((newItem.quantity || 0) * (newItem.unitPrice || 0) -
|
|||
|
|
(newItem.quantity || 0) *
|
|||
|
|
(newItem.unitPrice || 0) *
|
|||
|
|
((newItem.discountRate || 0) / 100)) *
|
|||
|
|
((newItem.taxRate || 0) / 100),
|
|||
|
|
netAmount: calculateItemTotal(newItem),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const updatedItems = [...(formData.items || []), item];
|
|||
|
|
const totals = calculateInvoiceTotals(updatedItems);
|
|||
|
|
|
|||
|
|
setFormData((prev) => ({
|
|||
|
|
...prev,
|
|||
|
|
items: updatedItems,
|
|||
|
|
...totals,
|
|||
|
|
remainingAmount: totals.totalAmount - (prev.paidAmount || 0),
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
setNewItem({
|
|||
|
|
description: "",
|
|||
|
|
quantity: 1,
|
|||
|
|
unitPrice: 0,
|
|||
|
|
taxRate: 18,
|
|||
|
|
discountRate: 0,
|
|||
|
|
unit: "Adet",
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleRemoveItem = (itemId: string) => {
|
|||
|
|
const updatedItems =
|
|||
|
|
formData.items?.filter((item) => item.id !== itemId) || [];
|
|||
|
|
const totals = calculateInvoiceTotals(updatedItems);
|
|||
|
|
|
|||
|
|
setFormData((prev) => ({
|
|||
|
|
...prev,
|
|||
|
|
items: updatedItems,
|
|||
|
|
...totals,
|
|||
|
|
remainingAmount: totals.totalAmount - (prev.paidAmount || 0),
|
|||
|
|
}));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
if (!formData.currentAccountId) {
|
|||
|
|
alert("Lütfen cari hesap seçin");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (!formData.items?.length) {
|
|||
|
|
alert("Lütfen en az bir ürün ekleyin");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onSave({
|
|||
|
|
...formData,
|
|||
|
|
id: invoice?.id || Date.now().toString(),
|
|||
|
|
creationTime: invoice?.creationTime || new Date(),
|
|||
|
|
lastModificationTime: new Date(),
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const formatCurrency = (amount: number) => {
|
|||
|
|
return amount.toLocaleString("tr-TR", {
|
|||
|
|
style: "currency",
|
|||
|
|
currency: "TRY",
|
|||
|
|
minimumFractionDigits: 2,
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (!isVisible) return null;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4">
|
|||
|
|
<div className="bg-white rounded-lg shadow-xl max-w-5xl w-full max-h-[90vh] overflow-y-auto">
|
|||
|
|
<div className="p-4 border-b border-gray-200">
|
|||
|
|
<div className="flex items-center justify-between">
|
|||
|
|
<div className="flex items-center gap-2.5">
|
|||
|
|
<FaFileAlt className="w-5 h-5 text-blue-600" />
|
|||
|
|
<h2 className="text-lg font-semibold text-gray-900">
|
|||
|
|
{invoice ? "Fatura Düzenle" : "Yeni Fatura"}
|
|||
|
|
</h2>
|
|||
|
|
</div>
|
|||
|
|
<button
|
|||
|
|
onClick={onCancel}
|
|||
|
|
className="p-2 hover:bg-gray-100 rounded-md transition-colors"
|
|||
|
|
>
|
|||
|
|
<FaTimes className="w-5 h-5 text-gray-500" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<form onSubmit={handleSubmit} className="p-4">
|
|||
|
|
{/* Basic Information */}
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Fatura No
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
value={formData.invoiceNumber}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setFormData({ ...formData, invoiceNumber: e.target.value })
|
|||
|
|
}
|
|||
|
|
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"
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Fatura Türü
|
|||
|
|
</label>
|
|||
|
|
<select
|
|||
|
|
value={formData.invoiceType}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setFormData({
|
|||
|
|
...formData,
|
|||
|
|
invoiceType: e.target.value as InvoiceTypeEnum,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
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"
|
|||
|
|
>
|
|||
|
|
<option value={InvoiceTypeEnum.Sales}>Satış</option>
|
|||
|
|
<option value={InvoiceTypeEnum.Purchase}>Alış</option>
|
|||
|
|
<option value={InvoiceTypeEnum.Return}>İade</option>
|
|||
|
|
<option value={InvoiceTypeEnum.Proforma}>Proforma</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
İrsaliye No
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
value={formData.waybillNumber || ""}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setFormData({ ...formData, waybillNumber: e.target.value })
|
|||
|
|
}
|
|||
|
|
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"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Cari Hesap
|
|||
|
|
</label>
|
|||
|
|
<select
|
|||
|
|
value={formData.currentAccountId}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setFormData({ ...formData, currentAccountId: e.target.value })
|
|||
|
|
}
|
|||
|
|
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"
|
|||
|
|
required
|
|||
|
|
>
|
|||
|
|
<option value="">Cari Hesap Seçin</option>
|
|||
|
|
{mockCurrentAccounts.map((account) => (
|
|||
|
|
<option key={account.id} value={account.id}>
|
|||
|
|
{account.accountCode}
|
|||
|
|
</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Fatura Tarihi
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="date"
|
|||
|
|
value={formData.invoiceDate?.toISOString().split("T")[0]}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setFormData({
|
|||
|
|
...formData,
|
|||
|
|
invoiceDate: new Date(e.target.value),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
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"
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Vade Tarihi
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="date"
|
|||
|
|
value={formData.dueDate?.toISOString().split("T")[0]}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setFormData({
|
|||
|
|
...formData,
|
|||
|
|
dueDate: new Date(e.target.value),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
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"
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Invoice Items */}
|
|||
|
|
<div className="mb-6">
|
|||
|
|
<h3 className="text-base font-semibold text-gray-900 mb-3">
|
|||
|
|
Fatura Kalemleri
|
|||
|
|
</h3>
|
|||
|
|
|
|||
|
|
{/* Add New Item */}
|
|||
|
|
<div className="bg-gray-50 p-3 rounded-lg mb-3">
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-6 gap-3">
|
|||
|
|
<div className="md:col-span-2">
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|||
|
|
Ürün/Hizmet Açıklaması
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
value={newItem.description || ""}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setNewItem({ ...newItem, description: e.target.value })
|
|||
|
|
}
|
|||
|
|
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="Ürün/Hizmet açıklaması"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|||
|
|
Miktar
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
value={newItem.quantity || ""}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setNewItem({
|
|||
|
|
...newItem,
|
|||
|
|
quantity: parseFloat(e.target.value) || 0,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
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"
|
|||
|
|
step="0.01"
|
|||
|
|
min="0"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|||
|
|
Birim Fiyat
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
value={newItem.unitPrice || ""}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setNewItem({
|
|||
|
|
...newItem,
|
|||
|
|
unitPrice: parseFloat(e.target.value) || 0,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
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"
|
|||
|
|
step="0.01"
|
|||
|
|
min="0"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|||
|
|
KDV (%)
|
|||
|
|
</label>
|
|||
|
|
<select
|
|||
|
|
value={newItem.taxRate || 18}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setNewItem({
|
|||
|
|
...newItem,
|
|||
|
|
taxRate: parseFloat(e.target.value),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
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"
|
|||
|
|
>
|
|||
|
|
<option value={0}>0%</option>
|
|||
|
|
<option value={1}>1%</option>
|
|||
|
|
<option value={8}>8%</option>
|
|||
|
|
<option value={18}>18%</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex items-end">
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
onClick={handleAddItem}
|
|||
|
|
className="w-full px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors flex items-center justify-center gap-2"
|
|||
|
|
>
|
|||
|
|
<FaPlus className="w-4 h-4" />
|
|||
|
|
Ekle
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Items List */}
|
|||
|
|
{formData.items && formData.items.length > 0 && (
|
|||
|
|
<div className="border border-gray-200 rounded-lg overflow-hidden">
|
|||
|
|
<table className="w-full">
|
|||
|
|
<thead className="bg-gray-50">
|
|||
|
|
<tr>
|
|||
|
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-700 uppercase">
|
|||
|
|
Açıklama
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-right text-xs font-medium text-gray-700 uppercase">
|
|||
|
|
Miktar
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-right text-xs font-medium text-gray-700 uppercase">
|
|||
|
|
Birim Fiyat
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-right text-xs font-medium text-gray-700 uppercase">
|
|||
|
|
KDV %
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-right text-xs font-medium text-gray-700 uppercase">
|
|||
|
|
Toplam
|
|||
|
|
</th>
|
|||
|
|
<th className="px-3 py-2 text-center text-xs font-medium text-gray-700 uppercase">
|
|||
|
|
İşlem
|
|||
|
|
</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody className="divide-y divide-gray-200">
|
|||
|
|
{formData.items.map((item) => (
|
|||
|
|
<tr key={item.id} className="text-sm">
|
|||
|
|
<td className="px-3 py-2 text-gray-900">
|
|||
|
|
{item.description}
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 text-gray-900 text-right">
|
|||
|
|
{item.quantity.toLocaleString("tr-TR")}
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 text-gray-900 text-right">
|
|||
|
|
{formatCurrency(item.unitPrice)}
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 text-gray-900 text-right">
|
|||
|
|
%{item.taxRate}
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 text-gray-900 text-right font-medium">
|
|||
|
|
{formatCurrency(item.lineTotal)}
|
|||
|
|
</td>
|
|||
|
|
<td className="px-3 py-2 text-center">
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
onClick={() => handleRemoveItem(item.id)}
|
|||
|
|
className="p-1 text-red-600 hover:bg-red-50 rounded"
|
|||
|
|
>
|
|||
|
|
<FaTrash className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</td>
|
|||
|
|
</tr>
|
|||
|
|
))}
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Totals */}
|
|||
|
|
<div className="bg-gray-50 p-4 rounded-lg mb-4">
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Notlar
|
|||
|
|
</label>
|
|||
|
|
<textarea
|
|||
|
|
value={formData.notes || ""}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setFormData({ ...formData, notes: e.target.value })
|
|||
|
|
}
|
|||
|
|
rows={4}
|
|||
|
|
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="Fatura ile ilgili notlar..."
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2 text-sm">
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<span className="text-sm text-gray-600">Ara Toplam:</span>
|
|||
|
|
<span className="font-medium">
|
|||
|
|
{formatCurrency(formData.subtotal || 0)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<span className="text-sm text-gray-600">İndirim:</span>
|
|||
|
|
<span className="font-medium text-red-600">
|
|||
|
|
-{formatCurrency(formData.discountAmount || 0)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<span className="text-sm text-gray-600">KDV:</span>
|
|||
|
|
<span className="font-medium">
|
|||
|
|
{formatCurrency(formData.taxAmount || 0)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="border-t pt-3">
|
|||
|
|
<div className="flex justify-between items-center">
|
|||
|
|
<span className="text-base font-semibold text-gray-900">
|
|||
|
|
Genel Toplam:
|
|||
|
|
</span>
|
|||
|
|
<span className="text-base font-bold text-blue-600">
|
|||
|
|
{formatCurrency(formData.totalAmount || 0)}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Actions */}
|
|||
|
|
<div className="flex justify-end gap-3">
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
onClick={onCancel}
|
|||
|
|
className="px-4 py-1.5 text-sm border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50 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 flex items-center gap-2"
|
|||
|
|
>
|
|||
|
|
<FaSave className="w-4 h-4" />
|
|||
|
|
{invoice ? "Güncelle" : "Kaydet"}
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</form>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default InvoiceForm;
|