diff --git a/Frontend/project-CL/src/features/academic/academic.tsx b/Frontend/project-CL/src/features/academic/academic.tsx index 8af1cbf..3e6ab06 100644 --- a/Frontend/project-CL/src/features/academic/academic.tsx +++ b/Frontend/project-CL/src/features/academic/academic.tsx @@ -123,13 +123,12 @@ export default function Academic() { - diff --git a/Frontend/project-CL/src/features/financial/components/AddCategoryModal.tsx b/Frontend/project-CL/src/features/financial/components/AddCategoryModal.tsx new file mode 100644 index 0000000..dabf039 --- /dev/null +++ b/Frontend/project-CL/src/features/financial/components/AddCategoryModal.tsx @@ -0,0 +1,130 @@ +import React, { useState, useEffect } from 'react'; +import { X } from 'lucide-react'; +import type { Category } from '../services/financialService'; + +interface AddCategoryModalProps { + isOpen: boolean; + onClose: () => void; + onSave: (name: string, type: 'income' | 'expense', bucket: string) => Promise; + category?: Category | null; // Optional category for editing +} + +export default function AddCategoryModal({ isOpen, onClose, onSave, category }: AddCategoryModalProps) { + const [name, setName] = useState(''); + const [type, setType] = useState<'income' | 'expense'>('expense'); + const [bucket, setBucket] = useState('needs'); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (isOpen && category) { + setName(category.name); + setType(category.type as 'income' | 'expense'); + setBucket(category.bucket); + } else if (isOpen && !category) { + // Reset for add mode + setName(''); + setType('expense'); + setBucket('needs'); + } + }, [isOpen, category]); + + if (!isOpen) return null; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + // If income, bucket might be irrelevant, but keeping it simple or setting to 'income' + const finalBucket = type === 'income' ? 'income' : bucket; + await onSave(name, type, finalBucket); + setLoading(false); + }; + + return ( +
+
+
+

{category ? 'Edit Category' : 'Add New Category'}

+ +
+ +
+ {/* Name Input */} +
+ + setName(e.target.value)} + placeholder="e.g., Groceries, Salary, Netflix" + className="w-full px-4 py-2 rounded-xl border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition-all" + /> +
+ + {/* Type Selection */} +
+ +
+ + +
+
+ + {/* Bucket Selection (Only for Expense) */} + {type === 'expense' && ( +
+ + +
+ )} + +
+ + +
+
+
+
+ ); +} diff --git a/Frontend/project-CL/src/features/financial/components/AddTransactionModal.tsx b/Frontend/project-CL/src/features/financial/components/AddTransactionModal.tsx new file mode 100644 index 0000000..7e4ef98 --- /dev/null +++ b/Frontend/project-CL/src/features/financial/components/AddTransactionModal.tsx @@ -0,0 +1,161 @@ +import React, { useState, useEffect } from 'react'; +import { X, Calendar, Tag } from 'lucide-react'; +import type { Category, Transaction } from '../services/financialService'; + +interface AddTransactionModalProps { + isOpen: boolean; + onClose: () => void; + onSave: (data: { category_id: number; amount: number; date: string; description: string }) => Promise; + categories: Category[]; + transaction?: Transaction | null; // Optional for edit mode +} + +export default function AddTransactionModal({ isOpen, onClose, onSave, categories, transaction }: AddTransactionModalProps) { + const [amount, setAmount] = useState(''); + const [categoryId, setCategoryId] = useState(''); + const [date, setDate] = useState(new Date().toISOString().split('T')[0]); + const [description, setDescription] = useState(''); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (isOpen && transaction) { + setAmount(transaction.amount.toString()); + setCategoryId(transaction.category_id); + // Ensure date is in YYYY-MM-DD format for input + setDate(new Date(transaction.date).toISOString().split('T')[0]); + setDescription(transaction.description); + } else if (isOpen) { + // Reset + setAmount(''); + setCategoryId(''); + setDate(new Date().toISOString().split('T')[0]); + setDescription(''); + } + }, [isOpen, transaction]); + + if (!isOpen) return null; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!categoryId || !amount) return; + + setLoading(true); + await onSave({ + category_id: Number(categoryId), + amount: Number(amount), + date: new Date(date).toISOString(), + description + }); + setLoading(false); + + // Reset form + setAmount(''); + setCategoryId(''); + setDate(new Date().toISOString().split('T')[0]); + setDescription(''); + }; + + return ( +
+
+
+

{transaction ? 'Edit Transaction' : 'Add Transaction'}

+ +
+ +
+ + {/* Amount Input */} +
+ +
+
+ Rp +
+ setAmount(e.target.value)} + placeholder="0" + className="w-full pl-10 pr-4 py-2 rounded-xl border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition-all font-mono font-medium" + /> +
+
+ + {/* Category Selection */} +
+ +
+
+ +
+ +
+
+ + {/* Date Input */} +
+ +
+
+ +
+ setDate(e.target.value)} + className="w-full pl-10 pr-4 py-2 rounded-xl border border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 outline-none transition-all" + /> +
+
+ + {/* Description Input */} +
+ +