Skip to content
Merged

Dev #35

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
17 changes: 10 additions & 7 deletions Backend/internal/finance/modelDB.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@ type Transaction struct {
Category Category `gorm:"foreignkey:CategoryID" json:"category"`
Date time.Time `gorm:"not null" json:"date"`
Amount float64 `gorm:"not null" json:"amount"`
Source string `gorm:"size:255" json:"source"`
Description string `gorm:"size:255;" json:"description"`
}

type Goal struct {
ID uint `gorm:"primary_key" json:"id"`
UserID uint `gorm:"index" json:"user_id"`
Name string `gorm:"size:255;not null" json:"name"`
TargetAmount float64 `gorm:"not null" json:"target_amount"`
SavedAmount float64 `gorm:"not null" json:"saved_amount"`
Status string `gorm:"size:255;default:'active'" json:"status"`
Description string `gorm:"size:255;" json:"description"`
ID uint `gorm:"primary_key" json:"id"`
UserID uint `gorm:"index" json:"user_id"`
Name string `gorm:"size:255;not null" json:"name"`
TargetAmount float64 `gorm:"not null" json:"target_amount"`
SavedAmount float64 `gorm:"not null" json:"saved_amount"`
TargetDaily float64 `gorm:"not null" json:"target_daily"`
Status string `gorm:"size:255;default:'active'" json:"status"`
Description string `gorm:"size:255;" json:"description"`
Deadline time.Time `json:"deadline"`
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import { useState, useEffect } from 'react';
import { X } from 'lucide-react';
import type { Category } from '../services/financialService';

Expand Down
172 changes: 172 additions & 0 deletions Frontend/project-CL/src/features/financial/components/AddGoalModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { X, Target, DollarSign, Clock, AlertCircle } from 'lucide-react';
import type { Goal } from '../services/financialService';
import { useGoalForm } from '../hooks/useGoalForm';

interface AddGoalModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (data: any) => Promise<void>;
goal?: Goal | null;
}

const AddGoalModal: React.FC<AddGoalModalProps> = ({ isOpen, onClose, onSave, goal }) => {
const { formState, setters, handlers } = useGoalForm(isOpen, goal, onSave, onClose);
const { name, targetAmount, savedAmount, targetDaily, deadline, description, status, isSubmitting } = formState;

if (!isOpen) return null;

return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
<div className="bg-white rounded-2xl w-full max-w-lg shadow-2xl transform transition-all flex flex-col max-h-[90vh] overflow-hidden">
{/* Header */}
<div className="p-6 border-b border-slate-100 flex justify-between items-center bg-slate-50">
<h3 className="text-xl font-bold text-slate-800 flex items-center gap-2">
<Target className="text-blue-600" size={24} />
{goal ? 'Edit Goal' : 'New Goal'}
</h3>
<button onClick={onClose} className="p-2 hover:bg-slate-200 rounded-full transition-colors text-slate-500">
<X size={20} />
</button>
</div>

{/* Form */}
<div className="overflow-y-auto p-6">
<form onSubmit={handlers.handleSubmit} className="space-y-5">
{/* Name */}
<div>
<label className="block text-sm font-semibold text-slate-700 mb-1">Goal Name</label>
<input
type="text"
required
value={name}
onChange={(e) => setters.setName(e.target.value)}
className="w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 transition-all outline-none text-slate-700 font-medium placeholder:text-slate-400"
placeholder="e.g. New Macbook"
/>
</div>

{/* Money Row */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-semibold text-slate-700 mb-1">Target Amount</label>
<div className="relative">
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400">
<DollarSign size={16} />
</div>
<input
type="text"
required
value={targetAmount}
onChange={(e) => handlers.handleTargetAmountChange(e.target.value)}
className="w-full pl-9 pr-4 py-2.5 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 transition-all outline-none text-slate-700 font-medium"
placeholder="0"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-slate-700 mb-1">Current Savings</label>
<div className="relative">
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400">
<DollarSign size={16} />
</div>
<input
type="text"
value={savedAmount}
onChange={(e) => handlers.handleSavedAmountChange(e.target.value)}
className="w-full pl-9 pr-4 py-2.5 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 transition-all outline-none text-slate-700 font-medium"
placeholder="0"
/>
</div>
</div>
</div>

{/* Planning Row */}
<div className="grid grid-cols-2 gap-4 bg-blue-50/50 p-4 rounded-xl border border-blue-100">
<div className="col-span-2 flex items-center gap-2 text-xs font-semibold text-blue-600 uppercase tracking-wider mb-1">
<Clock size={12} />
Planning Strategy
</div>

{/* Target Daily */}
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1">Target Daily Saving</label>
<div className="relative">
<input
type="text"
value={targetDaily}
onChange={(e) => handlers.handleTargetDailyChange(e.target.value)}
className="w-full px-3 py-2 rounded-lg border border-slate-200 focus:border-blue-500 focus:ring-1 focus:ring-blue-200 outline-none text-slate-700 text-sm"
placeholder="Auto if deadline set"
/>
</div>
</div>

{/* Deadline */}
<div>
<label className="block text-xs font-semibold text-slate-600 mb-1">Target Deadline</label>
<div className="relative">
<input
type="date"
value={deadline}
onChange={(e) => handlers.handleDeadlineChange(e.target.value)}
className="w-full px-3 py-2 rounded-lg border border-slate-200 focus:border-blue-500 focus:ring-1 focus:ring-blue-200 outline-none text-slate-700 text-sm"
/>
</div>
</div>

<div className="col-span-2 text-[10px] text-blue-400/80 flex items-start gap-1">
<AlertCircle size={10} className="mt-0.5" />
<span>Adjust either field above to auto-calculate the other based on remaining amount.</span>
</div>
</div>

{/* Status */}
<div>
<label className="block text-sm font-semibold text-slate-700 mb-1">Status</label>
<select
value={status}
onChange={(e) => setters.setStatus(e.target.value)}
className="w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 transition-all outline-none text-slate-700 font-medium bg-white"
>
<option value="active">Active (On Progress)</option>
<option value="paused">Paused</option>
<option value="completed">Completed</option>
</select>
</div>

{/* Description */}
<div>
<label className="block text-sm font-semibold text-slate-700 mb-1">Description <span className="text-slate-400 font-normal">(Optional)</span></label>
<textarea
value={description}
onChange={(e) => setters.setDescription(e.target.value)}
rows={3}
className="w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 transition-all outline-none text-slate-700 resize-none header-input placeholder:text-slate-400"
placeholder="Why do you want this?"
/>
</div>

{/* Actions */}
<div className="pt-2">
<button
type="submit"
disabled={isSubmitting}
className="w-full py-3 bg-slate-900 hover:bg-black text-white rounded-xl font-bold text-lg shadow-lg hover:shadow-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
{isSubmitting ? (
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
) : (
<>
{goal ? 'Save Changes' : 'Create Goal'}
</>
)}
</button>
</div>
</form>
</div>
</div>
</div>
);
};

export default AddGoalModal;
Loading