Skip to content
Merged

Dev #28

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Frontend/project-CL/src/features/academic/academic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,12 @@ export default function Academic() {
<ViewButton id="calendar" label="Calendar" icon={Calendar} />
<ViewButton id="timeline" label="Timeline" icon={Clock} />
</div>

<button
onClick={() => {
if (view === 'grid') setAddingSubject(true);
else setCreatingTask(true);
}}
className="px-6 py-2.5 bg-slate-900 text-white rounded-xl hover:bg-black font-bold transition-all shadow-lg flex items-center gap-2"
className={`${view === 'home' ? 'hidden' : ''} px-6 py-2.5 bg-slate-900 text-white rounded-xl hover:bg-black font-bold transition-all shadow-lg flex items-center gap-2`}
>
<span>+ {view === 'grid' ? 'Subject' : 'Task'}</span>
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<void>;
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 (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4">
<div className="bg-white rounded-2xl shadow-xl w-full max-w-md overflow-hidden transform transition-all">
<div className="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
<h2 className="text-xl font-bold text-gray-800">{category ? 'Edit Category' : 'Add New Category'}</h2>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 transition-colors">
<X size={24} />
</button>
</div>

<form onSubmit={handleSubmit} className="p-6 space-y-4">
{/* Name Input */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Category Name</label>
<input
type="text"
required
value={name}
onChange={(e) => 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"
/>
</div>

{/* Type Selection */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Type</label>
<div className="grid grid-cols-2 gap-3">
<button
type="button"
onClick={() => setType('income')}
className={`py-2 rounded-xl font-medium transition-all border ${type === 'income'
? 'bg-emerald-50 border-emerald-200 text-emerald-600 ring-2 ring-emerald-100'
: 'bg-white border-gray-200 text-gray-600 hover:bg-gray-50'
}`}
>
Income
</button>
<button
type="button"
onClick={() => setType('expense')}
className={`py-2 rounded-xl font-medium transition-all border ${type === 'expense'
? 'bg-rose-50 border-rose-200 text-rose-600 ring-2 ring-rose-100'
: 'bg-white border-gray-200 text-gray-600 hover:bg-gray-50'
}`}
>
Expense
</button>
</div>
</div>

{/* Bucket Selection (Only for Expense) */}
{type === 'expense' && (
<div className="animate-in fade-in slide-in-from-top-2 duration-200">
<label className="block text-sm font-semibold text-gray-700 mb-1">Bucket</label>
<select
value={bucket}
onChange={(e) => setBucket(e.target.value)}
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 appearance-none bg-white"
>
<option value="needs">Needs (Essentials)</option>
<option value="wants">Wants (Lifestyle)</option>
<option value="saving">Saving (Future)</option>
<option value="invest">Invest (Growth)</option>
</select>
</div>
)}

<div className="pt-4 flex gap-3">
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-2 rounded-xl border border-gray-200 text-gray-600 font-semibold hover:bg-gray-50 transition-all"
>
Cancel
</button>
<button
type="submit"
disabled={loading}
className="flex-1 px-4 py-2 rounded-xl bg-slate-900 text-white font-bold hover:bg-black transition-all shadow-lg shadow-slate-200 disabled:opacity-70 disabled:cursor-not-allowed"
>
{loading ? 'Saving...' : (category ? 'Save Changes' : 'Create Category')}
</button>
</div>
</form>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -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<void>;
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<number | ''>('');
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 (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4">
<div className="bg-white rounded-2xl shadow-xl w-full max-w-md overflow-hidden transform transition-all">
<div className="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
<h2 className="text-xl font-bold text-gray-800">{transaction ? 'Edit Transaction' : 'Add Transaction'}</h2>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 transition-colors">
<X size={24} />
</button>
</div>

<form onSubmit={handleSubmit} className="p-6 space-y-4">

{/* Amount Input */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Amount</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
<span className="font-bold text-sm">Rp</span>
</div>
<input
type="number"
required
min="0"
value={amount}
onChange={(e) => 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"
/>
</div>
</div>

{/* Category Selection */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Category</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
<Tag size={16} />
</div>
<select
required
value={categoryId}
onChange={(e) => setCategoryId(Number(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 appearance-none bg-white"
>
<option value="" disabled>Select Category</option>
{categories.map(cat => (
<option key={cat.id} value={cat.id}>
{cat.name} ({cat.type})
</option>
))}
</select>
</div>
</div>

{/* Date Input */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Date</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-gray-400">
<Calendar size={16} />
</div>
<input
type="date"
required
value={date}
onChange={(e) => 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"
/>
</div>
</div>

{/* Description Input */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-1">Description (Optional)</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="What was this for?"
rows={3}
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 resize-none"
/>
</div>

<div className="pt-4 flex gap-3">
<button
type="button"
onClick={onClose}
className="flex-1 px-4 py-2 rounded-xl border border-gray-200 text-gray-600 font-semibold hover:bg-gray-50 transition-all"
>
Cancel
</button>
<button
type="submit"
disabled={loading}
className="flex-1 px-4 py-2 rounded-xl bg-slate-900 text-white font-bold hover:bg-black transition-all shadow-lg shadow-slate-200 disabled:opacity-70 disabled:cursor-not-allowed"
>
{loading ? 'Saving...' : (transaction ? 'Save Changes' : 'Add Transaction')}
</button>
</div>
</form>
</div>
</div>
);
}
Loading