uup
This commit is contained in:
195
app/components/expenses/ExpenseForm.tsx
Normal file
195
app/components/expenses/ExpenseForm.tsx
Normal file
@@ -0,0 +1,195 @@
|
||||
import { Form } from "@remix-run/react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Input } from "~/components/ui/Input";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import { Flex } from "~/components/layout/Flex";
|
||||
import { EXPENSE_CATEGORIES } from "~/lib/constants";
|
||||
import type { Expense } from "@prisma/client";
|
||||
|
||||
interface ExpenseFormProps {
|
||||
expense?: Expense;
|
||||
onCancel: () => void;
|
||||
errors?: Record<string, string>;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export function ExpenseForm({
|
||||
expense,
|
||||
onCancel,
|
||||
errors = {},
|
||||
isLoading,
|
||||
}: ExpenseFormProps) {
|
||||
const [formData, setFormData] = useState({
|
||||
description: expense?.description || "",
|
||||
category: expense?.category || "",
|
||||
amount: expense?.amount?.toString() || "",
|
||||
expenseDate: expense?.expenseDate
|
||||
? new Date(expense.expenseDate).toISOString().split('T')[0]
|
||||
: new Date().toISOString().split('T')[0],
|
||||
});
|
||||
|
||||
// Reset form data when expense changes
|
||||
useEffect(() => {
|
||||
if (expense) {
|
||||
setFormData({
|
||||
description: expense.description || "",
|
||||
category: expense.category || "",
|
||||
amount: expense.amount?.toString() || "",
|
||||
expenseDate: expense.expenseDate
|
||||
? new Date(expense.expenseDate).toISOString().split('T')[0]
|
||||
: new Date().toISOString().split('T')[0],
|
||||
});
|
||||
} else {
|
||||
setFormData({
|
||||
description: "",
|
||||
category: "",
|
||||
amount: "",
|
||||
expenseDate: new Date().toISOString().split('T')[0],
|
||||
});
|
||||
}
|
||||
}, [expense]);
|
||||
|
||||
const handleInputChange = (field: string, value: string) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const isEditing = !!expense;
|
||||
|
||||
return (
|
||||
<Form method="post" className="space-y-6">
|
||||
<input
|
||||
type="hidden"
|
||||
name="_action"
|
||||
value={isEditing ? "update" : "create"}
|
||||
/>
|
||||
{isEditing && (
|
||||
<input type="hidden" name="id" value={expense.id} />
|
||||
)}
|
||||
|
||||
{/* Description */}
|
||||
<div>
|
||||
<label htmlFor="description" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
وصف المصروف *
|
||||
</label>
|
||||
<Input
|
||||
id="description"
|
||||
name="description"
|
||||
type="text"
|
||||
value={formData.description}
|
||||
onChange={(e) => handleInputChange("description", e.target.value)}
|
||||
placeholder="أدخل وصف المصروف"
|
||||
error={errors.description}
|
||||
required
|
||||
disabled={isLoading}
|
||||
/>
|
||||
{errors.description && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Category */}
|
||||
<div>
|
||||
<label htmlFor="category" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
الفئة *
|
||||
</label>
|
||||
<select
|
||||
id="category"
|
||||
name="category"
|
||||
value={formData.category}
|
||||
onChange={(e) => handleInputChange("category", e.target.value)}
|
||||
required
|
||||
disabled={isLoading}
|
||||
className={`
|
||||
w-full px-3 py-2 border rounded-lg shadow-sm
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
|
||||
disabled:bg-gray-50 disabled:text-gray-500
|
||||
${errors.category
|
||||
? 'border-red-300 focus:ring-red-500 focus:border-red-500'
|
||||
: 'border-gray-300'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<option value="">اختر الفئة</option>
|
||||
{EXPENSE_CATEGORIES.map((cat) => (
|
||||
<option key={cat.value} value={cat.value}>
|
||||
{cat.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{errors.category && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.category}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Amount */}
|
||||
<div>
|
||||
<label htmlFor="amount" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
المبلغ *
|
||||
</label>
|
||||
<Input
|
||||
id="amount"
|
||||
name="amount"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
value={formData.amount}
|
||||
onChange={(e) => handleInputChange("amount", e.target.value)}
|
||||
placeholder="0.00"
|
||||
error={errors.amount}
|
||||
required
|
||||
disabled={isLoading}
|
||||
dir="ltr"
|
||||
/>
|
||||
{errors.amount && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.amount}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Expense Date */}
|
||||
<div>
|
||||
<label htmlFor="expenseDate" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
تاريخ المصروف
|
||||
</label>
|
||||
<Input
|
||||
id="expenseDate"
|
||||
name="expenseDate"
|
||||
type="date"
|
||||
value={formData.expenseDate}
|
||||
onChange={(e) => handleInputChange("expenseDate", e.target.value)}
|
||||
error={errors.expenseDate}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
{errors.expenseDate && (
|
||||
<p className="mt-1 text-sm text-red-600">{errors.expenseDate}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Form Actions */}
|
||||
<Flex justify="end" className="pt-4 gap-2 border-t">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={onCancel}
|
||||
disabled={isLoading}
|
||||
className="w-20"
|
||||
>
|
||||
إلغاء
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isLoading || !formData.description.trim() || !formData.category || !formData.amount}
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
{isLoading
|
||||
? (isEditing ? "جاري التحديث..." : "جاري الإنشاء...")
|
||||
: (isEditing ? "تحديث المصروف" : "إنشاء المصروف")
|
||||
}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user