feat: integrate ai jars into budget, rename tabs, and fix dashboard navigation
This commit is contained in:
70
NAVIGATION_AUDIT.md
Normal file
70
NAVIGATION_AUDIT.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Navigation & Route Audit
|
||||
Last Updated: 2026-02-16
|
||||
|
||||
## ✅ Working Routes
|
||||
| Page/Component | Element | Route/Action | Status |
|
||||
|----------------|---------|--------------|--------|
|
||||
| Layout | "Home", "Portfolio", "Budget", etc. | `onSectionChange` (State) | ✅ Working |
|
||||
| Layout | "Logout" | `onLogout` (Auth Hook) | ✅ Working |
|
||||
| Profile | "Theme Switcher" | `setTheme` (Context) | ✅ Working |
|
||||
| Savings | "Calculator" (Sliders/Input) | Local State | ✅ Working |
|
||||
| Dashboard | "HeroStats" (Cards) | Display Only | ✅ Working |
|
||||
| Portfolio | "Risk Analysis" (Cards) | Display Only | ✅ Working |
|
||||
|
||||
## ❌ Non-Functional Elements
|
||||
These elements are visually present but lack `onClick` handlers or backend integration.
|
||||
|
||||
| Page/Component | Element | Expected Action | Issue |
|
||||
|----------------|---------|-----------------|-------|
|
||||
| **Dashboard** | | | |
|
||||
| `TransactionsList` | "View All Transactions" button | `onSectionChange('budget')` or route | No handler |
|
||||
| `GoalsList` | "Manage Goals" button | `onSectionChange('goals')` | No handler |
|
||||
| `QuickActions` | "New Investment" card | Open Modal / Navigate | No handler |
|
||||
| `QuickActions` | "Set Goals" card | Open Modal / Navigate | No handler |
|
||||
| `QuickActions` | "View Reports" card | Open Modal / Navigate | No handler |
|
||||
| `QuickActions` | "Savings Plan" card | Navigate to `savings` | No handler |
|
||||
| **Portfolio** | | | |
|
||||
| `QuickInvestmentForm` | "Buy Asset" button | Execute Trade / Modal | No handler |
|
||||
| `QuickInvestmentForm` | "Sell Asset" button | Execute Trade / Modal | No handler |
|
||||
| `HoldingsTable` | "+" / "-" Action buttons | Modify Position | No handler |
|
||||
| **Budget** | | | |
|
||||
| `AddExpenseForm` | "Add Expense" button | Submit Form | No handler |
|
||||
| `SetBudgetForm` | "Set Budget" button | Submit Form | No handler |
|
||||
| **Savings** | | | |
|
||||
| `SavingsGoalsGrid` | "+1K", "+5K", "Custom" buttons | Add Funds | No handler |
|
||||
| `SavingsAccountsTable` | "Settings" (Gear icon) | Open Settings | No handler |
|
||||
| `AddGoalForm` | "Create Savings Goal" button | Submit Form | No handler |
|
||||
| **Credit** | | | |
|
||||
| `CreditCardsList` | "Pay Custom" button | Payment Modal | No handler |
|
||||
| `CreditCardsList` | "Pay Min" button | Payment Modal | No handler |
|
||||
| `CreditCardsList` | "More" (Three dots) button | Menu / Details | No handler |
|
||||
| **Commitments** | | | |
|
||||
| `UpcomingCommitments` | "Mark Paid" (Check) button | Update Status | No handler |
|
||||
| `UpcomingCommitments` | "Delete" (Trash) button | Remove Item | No handler |
|
||||
| `UpcomingCommitments` | "View All History" button | Navigate / Modal | No handler |
|
||||
| `CommitmentAITips` | "View Credit Impact" button | Navigate / Modal | No handler |
|
||||
| `CommitmentCalendar` | "Mark Paid" / "Delete" buttons | Update/Remove | No handler |
|
||||
| `AddCommitmentForm` | "Add Commitment" button | Submit Form | No handler |
|
||||
|
||||
## 🚧 Placeholder Buttons (No Action)
|
||||
| Page/Component | Element | Notes |
|
||||
|----------------|---------|-------|
|
||||
| Dashboard | Hero Stat Cards | Could link to respective modules (e.g., Portfolio Value -> Portfolio) |
|
||||
| Dashboard | "Recent Transactions" Items | Could broaden transaction details modal |
|
||||
| Savings | "Savings Accounts" Items | Could open account details |
|
||||
| Credit | "Credit Cards" Items | Could open card details/statements |
|
||||
|
||||
## 📋 Recommended Actions
|
||||
**High Priority:**
|
||||
- [ ] **Dashboard Navigation:** Wire up `QuickActions` to navigate to respective modules (e.g., "New Investment" -> Portfolio, "Savings Plan" -> Savings).
|
||||
- [ ] **View All buttons:** Connect "View All Transactions" to the specific transaction history view (or Budget module).
|
||||
- [ ] **Forms:** Implement `onSubmit` handlers (even if just `console.log` + toast) for "Add Expense", "Set Budget", "Add Commitment", and "Add Goal".
|
||||
|
||||
**Medium Priority:**
|
||||
- [ ] **Interactive Actions:** Implement "Pay Min/Custom" logic in Credit Manager (show a payment success toast).
|
||||
- [ ] **List Actions:** Implementation delete/mark-paid logic for Commitments (update local state).
|
||||
- [ ] **Investment:** Add basic validation/toast for "Buy/Sell" in Portfolio.
|
||||
|
||||
**Low Priority:**
|
||||
- [ ] **Deep Linking:** Check/Trash buttons in tables.
|
||||
- [ ] **Settings:** Account-level settings in Savings module.
|
||||
BIN
dist.tar.gz
BIN
dist.tar.gz
Binary file not shown.
@@ -12,7 +12,11 @@ import InvestmentPerformance from './dashboard/InvestmentPerformance';
|
||||
import AIInsights from './dashboard/AIInsights';
|
||||
import QuickActions from './dashboard/QuickActions';
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
interface DashboardProps {
|
||||
onNavigate?: (section: string) => void;
|
||||
}
|
||||
|
||||
const Dashboard: React.FC<DashboardProps> = ({ onNavigate }) => {
|
||||
// Hardcoded Header Data as per Target Design Reference
|
||||
const currentDate = "Sunday, February 15, 2026";
|
||||
const monthlyWealthGrowth = "425,600"; // AED value
|
||||
@@ -63,12 +67,12 @@ const Dashboard: React.FC = () => {
|
||||
<section className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start relative z-10">
|
||||
{/* Left Column: Recent Transactions */}
|
||||
<div className="space-y-6">
|
||||
<TransactionsList />
|
||||
<TransactionsList onNavigate={onNavigate} />
|
||||
</div>
|
||||
|
||||
{/* Right Column: Financial Goals */}
|
||||
<div className="space-y-6">
|
||||
<GoalsList />
|
||||
<GoalsList onNavigate={onNavigate} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -88,7 +92,7 @@ const Dashboard: React.FC = () => {
|
||||
{/* Footer Actions */}
|
||||
<section className="relative z-10">
|
||||
<h3 className="text-lg font-medium text-slate-700 mb-4 px-1">Quick Actions</h3>
|
||||
<QuickActions />
|
||||
<QuickActions onNavigate={onNavigate} />
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,26 @@ import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const AddExpenseForm: React.FC = () => {
|
||||
const handleAddExpense = () => {
|
||||
const amount = (document.getElementById('amount') as HTMLInputElement).value;
|
||||
const desc = (document.getElementById('desc') as HTMLInputElement).value;
|
||||
const category = (document.getElementById('category') as HTMLButtonElement | null)?.innerText || "Category";
|
||||
|
||||
if (!amount || !desc) {
|
||||
toast.error("Please fill in amount and description");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success(`Expense Added: AED ${amount} to ${category}`);
|
||||
|
||||
// Clear
|
||||
(document.getElementById('amount') as HTMLInputElement).value = '';
|
||||
(document.getElementById('desc') as HTMLInputElement).value = '';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center h-[400px]">
|
||||
<GlassCard className="p-8 w-full max-w-md">
|
||||
@@ -45,7 +64,10 @@ const AddExpenseForm: React.FC = () => {
|
||||
<Input id="desc" placeholder="e.g. Grocery shopping" className="bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
|
||||
<Button className="w-full mt-4 bg-slate-900 text-white hover:bg-slate-800 shadow-md">
|
||||
<Button
|
||||
className="w-full mt-4 bg-slate-900 text-white hover:bg-slate-800 shadow-md"
|
||||
onClick={handleAddExpense}
|
||||
>
|
||||
Add Expense
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
162
src/components/budget/BudgetGoalPlanning.tsx
Normal file
162
src/components/budget/BudgetGoalPlanning.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import React, { useState } from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { Target, Zap, Plus, Sparkles } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const BudgetGoalPlanning: React.FC = () => {
|
||||
// Left Column Data: Active Jars/Goals
|
||||
const [goals, setGoals] = useState([
|
||||
{ id: 1, name: 'Rainy Day Fund', current: 4500, target: 10000, color: 'bg-blue-500' },
|
||||
{ id: 2, name: 'New Laptop', current: 3200, target: 8000, color: 'bg-purple-500' },
|
||||
{ id: 3, name: 'Holiday Gift Fund', current: 500, target: 2000, color: 'bg-pink-500' },
|
||||
]);
|
||||
|
||||
// Right Column State: AI Config
|
||||
const [aiEnabled, setAiEnabled] = useState(false);
|
||||
|
||||
const handleAddFunds = (name: string) => {
|
||||
toast.success(`Funds added to ${name}`);
|
||||
};
|
||||
|
||||
const handleActivateAI = () => {
|
||||
setAiEnabled(!aiEnabled);
|
||||
if (!aiEnabled) {
|
||||
toast.success("AI Auto-Save Activated!");
|
||||
} else {
|
||||
toast.info("AI Auto-Save Paused");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
|
||||
{/* Left Column: Active Jars & Goals */}
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="text-xl font-semibold text-slate-800 flex items-center gap-2">
|
||||
<Target className="w-5 h-5 text-indigo-500" /> Active Jars & Goals
|
||||
</h3>
|
||||
<Button variant="outline" size="sm" className="h-8 border-dashed border-slate-300 text-slate-500 hover:text-indigo-600 hover:border-indigo-300">
|
||||
<Plus className="w-3 h-3 mr-1" /> New Jar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{goals.map((goal) => {
|
||||
const progress = (goal.current / goal.target) * 100;
|
||||
return (
|
||||
<GlassCard key={goal.id} className="p-5 space-y-4 group hover:border-indigo-100 transition-all">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-800">{goal.name}</h4>
|
||||
<p className="text-sm text-slate-500 mt-1">
|
||||
<span className="font-medium text-slate-700"><DirhamIcon className="w-3 h-3 inline" /> {goal.current.toLocaleString()}</span>
|
||||
<span className="text-slate-400"> / {goal.target.toLocaleString()}</span>
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-7 text-xs border-slate-200 text-slate-600 hover:bg-slate-50"
|
||||
onClick={() => handleAddFunds(goal.name)}
|
||||
>
|
||||
Add Funds
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-xs text-slate-500 mb-1">
|
||||
<span>{progress.toFixed(0)}% Funded</span>
|
||||
<span>{Math.max(0, goal.target - goal.current).toLocaleString()} remaining</span>
|
||||
</div>
|
||||
<Progress value={progress} className="h-2" indicatorClassName={goal.color} />
|
||||
</div>
|
||||
</GlassCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column: AI Auto-Save Configuration */}
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-xl font-semibold text-slate-800 flex items-center gap-2">
|
||||
<Zap className="w-5 h-5 text-yellow-500 fill-yellow-500" /> Automate Your Savings
|
||||
</h3>
|
||||
|
||||
<GlassCard className="p-6 relative overflow-hidden">
|
||||
{/* Background decoration */}
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-br from-yellow-200/20 to-orange-200/20 rounded-bl-full -z-10" />
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between pb-4 border-b border-white/20">
|
||||
<div>
|
||||
<h4 className="font-semibold text-slate-800">Enable AI Logic</h4>
|
||||
<p className="text-xs text-slate-500">Allow WealthWise to save while you spend</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={aiEnabled}
|
||||
onCheckedChange={handleActivateAI}
|
||||
className="data-[state=checked]:bg-green-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={`space-y-5 transition-opacity duration-300 ${aiEnabled ? 'opacity-100' : 'opacity-50 pointer-events-none'}`}>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-slate-600 text-sm">Smart Rule</Label>
|
||||
<Select defaultValue="roundup">
|
||||
<SelectTrigger className="bg-white/50 border-slate-200">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="roundup">Round-up transactions to nearest AED 10</SelectItem>
|
||||
<SelectItem value="percentage">Save 1% of every purchase</SelectItem>
|
||||
<SelectItem value="fixed">Auto-transfer AED 5 daily</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-slate-600 text-sm">Target Jar</Label>
|
||||
<Select defaultValue="rainy">
|
||||
<SelectTrigger className="bg-white/50 border-slate-200">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="rainy">Rainy Day Fund</SelectItem>
|
||||
<SelectItem value="laptop">New Laptop</SelectItem>
|
||||
<SelectItem value="holiday">Holiday Gift Fund</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label className="text-slate-600 text-sm">Monthly Cap</Label>
|
||||
<div className="relative">
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-xs">AED</span>
|
||||
<Input type="number" defaultValue="500" className="pl-10 bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* AI Insight Box */}
|
||||
<div className="bg-indigo-50/50 border border-indigo-100 rounded-lg p-3 flex gap-3 items-start">
|
||||
<Sparkles className="w-4 h-4 text-indigo-500 mt-0.5 shrink-0" />
|
||||
<p className="text-xs text-indigo-800 leading-relaxed">
|
||||
Based on your spending habits, this rule will save approx <span className="font-semibold">AED 340/month</span> without impacting your daily budget needs.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BudgetGoalPlanning;
|
||||
@@ -7,7 +7,24 @@ import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const SetBudgetForm: React.FC = () => {
|
||||
const handleSetBudget = () => {
|
||||
const amount = (document.getElementById('budget-amount') as HTMLInputElement).value;
|
||||
const category = (document.getElementById('category-budget') as HTMLButtonElement | null)?.innerText || "Category";
|
||||
|
||||
if (!amount) {
|
||||
toast.error("Please enter a budget amount");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success(`Budget Updated: AED ${amount} for ${category}`);
|
||||
|
||||
// Clear
|
||||
(document.getElementById('budget-amount') as HTMLInputElement).value = '';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center h-[400px]">
|
||||
<GlassCard className="p-8 w-full max-w-md">
|
||||
@@ -40,7 +57,10 @@ const SetBudgetForm: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button className="w-full mt-4 bg-gradient-to-r from-blue-600 to-indigo-600 text-white hover:from-blue-700 hover:to-indigo-700 shadow-md">
|
||||
<Button
|
||||
className="w-full mt-4 bg-gradient-to-r from-blue-600 to-indigo-600 text-white hover:from-blue-700 hover:to-indigo-700 shadow-md"
|
||||
onClick={handleSetBudget}
|
||||
>
|
||||
Set Budget
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,26 @@ import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Sparkles } from 'lucide-react';
|
||||
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const AddCommitmentForm: React.FC = () => {
|
||||
const handleAdd = () => {
|
||||
const name = (document.getElementById('comm-name') as HTMLInputElement).value;
|
||||
const amount = (document.getElementById('comm-amount') as HTMLInputElement).value;
|
||||
|
||||
if (!name || !amount) {
|
||||
toast.error("Please fill in name and amount");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success(`Commitment Added: ${name}`);
|
||||
|
||||
// Reset
|
||||
(document.getElementById('comm-name') as HTMLInputElement).value = '';
|
||||
(document.getElementById('comm-amount') as HTMLInputElement).value = '';
|
||||
(document.getElementById('comm-type') as HTMLInputElement).value = '';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center h-[500px]">
|
||||
<GlassCard className="p-8 w-full max-w-md">
|
||||
@@ -33,7 +52,10 @@ const AddCommitmentForm: React.FC = () => {
|
||||
<Input id="comm-type" placeholder="e.g. Insurance, Utility" className="bg-white/50 border-slate-200" />
|
||||
</div>
|
||||
|
||||
<Button className="w-full mt-4 bg-slate-900 text-white hover:bg-slate-800 shadow-md flex items-center gap-2 justify-center">
|
||||
<Button
|
||||
className="w-full mt-4 bg-slate-900 text-white hover:bg-slate-800 shadow-md flex items-center gap-2 justify-center"
|
||||
onClick={handleAdd}
|
||||
>
|
||||
<Sparkles className="w-4 h-4 text-yellow-400" /> Add Commitment
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { Home, CreditCard, Zap, Trash2, Check } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const UpcomingCommitmentsList: React.FC = () => {
|
||||
const commitments = [
|
||||
@@ -63,10 +64,20 @@ const UpcomingCommitmentsList: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Button variant="outline" size="sm" className="h-8 border-slate-900 text-slate-900 bg-white hover:bg-slate-50">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 border-slate-900 text-slate-900 bg-white hover:bg-slate-50"
|
||||
onClick={() => toast.success(`Marked as Paid: ${item.title}`)}
|
||||
>
|
||||
<Check className="w-3 h-3 mr-1" /> Mark Paid
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 text-red-500 hover:text-red-600 hover:bg-red-50">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0 text-red-500 hover:text-red-600 hover:bg-red-50"
|
||||
onClick={() => toast.success(`Commitment Deleted: ${item.title}`)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { formatAmount } from '@/lib/utils';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { MoreHorizontal } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const CreditCardsList: React.FC = () => {
|
||||
|
||||
@@ -108,10 +109,17 @@ const CreditCardsList: React.FC = () => {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-3 w-full sm:w-auto">
|
||||
<Button variant="outline" className="flex-1 sm:flex-none border-slate-200 text-slate-700 hover:bg-slate-50">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex-1 sm:flex-none border-slate-200 text-slate-700 hover:bg-slate-50"
|
||||
onClick={() => toast.info("Custom payment flow would open here")}
|
||||
>
|
||||
Pay Custom
|
||||
</Button>
|
||||
<Button className="flex-1 sm:flex-none bg-black text-white hover:bg-slate-800">
|
||||
<Button
|
||||
className="flex-1 sm:flex-none bg-black text-white hover:bg-slate-800"
|
||||
onClick={() => toast.success(`Payment Processed: AED ${card.minDue} for ${card.name}`)}
|
||||
>
|
||||
Pay Min
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,11 @@ import { Button } from '@/components/ui/button';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Target } from 'lucide-react';
|
||||
|
||||
const GoalsList: React.FC = () => {
|
||||
interface GoalsListProps {
|
||||
onNavigate?: (section: string) => void;
|
||||
}
|
||||
|
||||
const GoalsList: React.FC<GoalsListProps> = ({ onNavigate }) => {
|
||||
const goals = [
|
||||
{
|
||||
name: 'Emergency Fund',
|
||||
@@ -53,7 +57,10 @@ const GoalsList: React.FC = () => {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button className="w-full mt-6 bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white shadow-md">
|
||||
<Button
|
||||
className="w-full mt-6 bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white shadow-md"
|
||||
onClick={() => onNavigate?.('budget')} // Now part of Budget Manager
|
||||
>
|
||||
Manage Goals
|
||||
</Button>
|
||||
</GlassCard>
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { HoverPreview } from '@/components/ui/HoverPreview'; // Import HoverPreview
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import {
|
||||
TrendingUp,
|
||||
DollarSign,
|
||||
PieChart,
|
||||
Shield
|
||||
Shield,
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
CheckCircle2
|
||||
} from 'lucide-react';
|
||||
import { formatAmount } from '@/lib/utils'; // Keep import for potential reuse, though hardcoded values requested for now
|
||||
import { Progress } from '@/components/ui/progress'; // Assuming we have Progress for risk or charts
|
||||
// import { formatAmount } from '@/lib/utils'; // Keep import for potential reuse
|
||||
|
||||
const HeroStats: React.FC = () => {
|
||||
// Hardcoded data as per Target Design Reference
|
||||
@@ -20,7 +24,26 @@ const HeroStats: React.FC = () => {
|
||||
isPositive: true,
|
||||
icon: <TrendingUp className="h-6 w-6 text-white" />,
|
||||
color: 'from-purple-500 to-indigo-600',
|
||||
iconBg: 'bg-white/20'
|
||||
iconBg: 'bg-white/20',
|
||||
preview: (
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-semibold text-slate-800">Top Holdings</h4>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ name: 'Apple (AAPL)', change: '+1.4%', up: true },
|
||||
{ name: 'Bitcoin (BTC)', change: '+5.2%', up: true },
|
||||
{ name: 'Dubai Real Estate', change: '+0.8%', up: true },
|
||||
].map((item, i) => (
|
||||
<div key={i} className="flex justify-between items-center text-sm">
|
||||
<span className="text-slate-600">{item.name}</span>
|
||||
<span className="flex items-center text-green-600 font-medium text-xs">
|
||||
<ArrowUpRight className="w-3 h-3 mr-1" /> {item.change}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: 'Monthly Contributions',
|
||||
@@ -29,7 +52,26 @@ const HeroStats: React.FC = () => {
|
||||
isPositive: true,
|
||||
icon: <DollarSign className="h-6 w-6 text-white" />,
|
||||
color: 'from-blue-500 to-cyan-600',
|
||||
iconBg: 'bg-white/20'
|
||||
iconBg: 'bg-white/20',
|
||||
preview: (
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-semibold text-slate-800">Recent Deposits</h4>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ source: 'Salary', amount: '15k', date: '15th Feb' },
|
||||
{ source: 'Freelance', amount: '2.5k', date: '10th Feb' },
|
||||
].map((item, i) => (
|
||||
<div key={i} className="flex justify-between items-center text-sm">
|
||||
<div>
|
||||
<span className="text-slate-700 block font-medium">{item.source}</span>
|
||||
<span className="text-xs text-slate-400">{item.date}</span>
|
||||
</div>
|
||||
<span className="text-green-600 font-medium">+{item.amount}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: 'Annual Returns',
|
||||
@@ -39,44 +81,107 @@ const HeroStats: React.FC = () => {
|
||||
icon: <PieChart className="h-6 w-6 text-white" />,
|
||||
color: 'from-emerald-500 to-teal-600',
|
||||
iconBg: 'bg-white/20',
|
||||
isPercentage: true
|
||||
isPercentage: true,
|
||||
preview: (
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-semibold text-slate-800">Monthly Performance</h4>
|
||||
<div className="flex items-end gap-2 h-16 pt-2">
|
||||
{/* Simple CSS Bar Chart Simulation */}
|
||||
{[
|
||||
{ month: 'J', val: 70, color: 'bg-emerald-500' },
|
||||
{ month: 'F', val: 90, color: 'bg-emerald-600' },
|
||||
{ month: 'M', val: 40, color: 'bg-red-400' }, // Negative dip example
|
||||
{ month: 'A', val: 60, color: 'bg-emerald-500' },
|
||||
{ month: 'M', val: 80, color: 'bg-emerald-500' },
|
||||
{ month: 'J', val: 50, color: 'bg-emerald-400' },
|
||||
].map((d, i) => (
|
||||
<div key={i} className="flex-1 flex flex-col justify-end items-center gap-1 group">
|
||||
<div
|
||||
className={`w-full rounded-t-sm transition-all group-hover:opacity-80 ${d.color}`}
|
||||
style={{ height: `${d.val}%` }}
|
||||
/>
|
||||
<span className="text-[10px] text-slate-400 uppercase">{d.month}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-between text-xs text-slate-500 mt-1">
|
||||
<span>Jan (+2%)</span>
|
||||
<span>Feb (+3.1%)</span>
|
||||
<span>Mar (-0.5%)</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: 'Risk Score',
|
||||
value: '3.2/10',
|
||||
change: '-0.3',
|
||||
isPositive: true, // Lower risk score change might be positive contextually, but design shows -0.3. Assuming red for negative change generally unless specified. Design says (-0.3).
|
||||
isPositive: true,
|
||||
icon: <Shield className="h-6 w-6 text-white" />,
|
||||
color: 'from-orange-500 to-red-600',
|
||||
iconBg: 'bg-white/20',
|
||||
isScore: true
|
||||
isScore: true,
|
||||
preview: (
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-semibold text-slate-800">Risk Factors</h4>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ label: 'Volatility', status: 'Low', color: 'text-green-600', bg: 'bg-green-100' },
|
||||
{ label: 'Diversification', status: 'High', color: 'text-green-600', bg: 'bg-green-100' },
|
||||
{ label: 'Sector Exposure', status: 'Balanced', color: 'text-blue-600', bg: 'bg-blue-100' },
|
||||
].map((item, i) => (
|
||||
<div key={i} className="flex justify-between items-center text-sm">
|
||||
<span className="text-slate-600">{item.label}</span>
|
||||
<span className={`flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium ${item.bg} ${item.color}`}>
|
||||
<CheckCircle2 className="w-3 h-3" /> {item.status}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-2 pt-2 border-t border-slate-100">
|
||||
<div className="flex justify-between text-xs text-slate-500 mb-1">
|
||||
<span>Conservative</span>
|
||||
<span>Aggressive</span>
|
||||
</div>
|
||||
<Progress value={32} className="h-1.5" indicatorClassName="bg-gradient-to-r from-green-500 to-orange-500" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{stats.map((stat, idx) => (
|
||||
<GlassCard key={idx} className="p-6 relative overflow-hidden group">
|
||||
<div className="relative z-10">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className={`p-3 rounded-xl bg-gradient-to-br ${stat.color} shadow-lg`}>
|
||||
{stat.icon}
|
||||
</div>
|
||||
<span className={`text-xs font-semibold px-2 py-1 rounded-full flex items-center ${stat.change.startsWith('+') ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
|
||||
{stat.change}
|
||||
</span>
|
||||
<HoverPreview
|
||||
key={idx}
|
||||
trigger={
|
||||
<div className="h-full"> {/* Wrap in div to avoid ref issues with functional components if any, though GlassCard should handle it, explicit div is safer for trigger */}
|
||||
<GlassCard className="p-6 relative overflow-hidden group cursor-pointer h-full transition-transform hover:-translate-y-1 block">
|
||||
<div className="relative z-10">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className={`p-3 rounded-xl bg-gradient-to-br ${stat.color} shadow-lg`}>
|
||||
{stat.icon}
|
||||
</div>
|
||||
<span className={`text-xs font-semibold px-2 py-1 rounded-full flex items-center ${stat.change.startsWith('+') ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
|
||||
{stat.change}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-slate-500 font-medium">{stat.label}</p>
|
||||
<div className="flex items-center gap-1 mt-1">
|
||||
{!stat.isPercentage && !stat.isScore && <DirhamIcon className="w-5 h-5 text-slate-800" />}
|
||||
<h3 className="text-2xl font-bold text-slate-800">{stat.value}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Decorative background element */}
|
||||
<div className={`absolute -right-6 -bottom-6 w-24 h-24 rounded-full bg-gradient-to-br ${stat.color} opacity-10 blur-xl group-hover:opacity-20 transition-opacity duration-500`} />
|
||||
</GlassCard>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-slate-500 font-medium">{stat.label}</p>
|
||||
<div className="flex items-center gap-1 mt-1">
|
||||
{!stat.isPercentage && !stat.isScore && <DirhamIcon className="w-5 h-5 text-slate-800" />}
|
||||
<h3 className="text-2xl font-bold text-slate-800">{stat.value}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Decorative background element */}
|
||||
<div className={`absolute -right-6 -bottom-6 w-24 h-24 rounded-full bg-gradient-to-br ${stat.color} opacity-10 blur-xl group-hover:opacity-20 transition-opacity duration-500`} />
|
||||
</GlassCard>
|
||||
}
|
||||
content={stat.preview}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,27 +4,35 @@ import { GlassCard } from '@/components/ui/GlassCard';
|
||||
import { PlusCircle, Target, FileText, PiggyBank } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
const QuickActions: React.FC = () => {
|
||||
interface QuickActionsProps {
|
||||
onNavigate?: (section: string) => void;
|
||||
}
|
||||
|
||||
const QuickActions: React.FC<QuickActionsProps> = ({ onNavigate }) => {
|
||||
const actions = [
|
||||
{
|
||||
label: 'New Investment',
|
||||
icon: <PlusCircle className="w-5 h-5 mb-2" />,
|
||||
color: 'hover:border-green-200 hover:bg-green-50/50 hover:text-green-700'
|
||||
color: 'hover:border-green-200 hover:bg-green-50/50 hover:text-green-700',
|
||||
action: () => onNavigate?.('portfolio') // Default to overview
|
||||
},
|
||||
{
|
||||
label: 'Set Goals',
|
||||
icon: <Target className="w-5 h-5 mb-2" />,
|
||||
color: 'hover:border-blue-200 hover:bg-blue-50/50 hover:text-blue-700'
|
||||
color: 'hover:border-blue-200 hover:bg-blue-50/50 hover:text-blue-700',
|
||||
action: () => onNavigate?.('budget') // Now part of Budget Manager
|
||||
},
|
||||
{
|
||||
label: 'View Reports',
|
||||
icon: <FileText className="w-5 h-5 mb-2" />,
|
||||
color: 'hover:border-purple-200 hover:bg-purple-50/50 hover:text-purple-700'
|
||||
color: 'hover:border-purple-200 hover:bg-purple-50/50 hover:text-purple-700',
|
||||
action: () => onNavigate?.('budget')
|
||||
},
|
||||
{
|
||||
label: 'Savings Plan',
|
||||
icon: <PiggyBank className="w-5 h-5 mb-2" />,
|
||||
color: 'hover:border-pink-200 hover:bg-pink-50/50 hover:text-pink-700'
|
||||
color: 'hover:border-pink-200 hover:bg-pink-50/50 hover:text-pink-700',
|
||||
action: () => onNavigate?.('savings')
|
||||
}
|
||||
];
|
||||
|
||||
@@ -35,6 +43,7 @@ const QuickActions: React.FC = () => {
|
||||
key={idx}
|
||||
className={`p-4 flex flex-col items-center justify-center cursor-pointer transition-all active:scale-95 ${action.color} group`}
|
||||
hoverEffect
|
||||
onClick={action.action}
|
||||
>
|
||||
<div className="p-3 rounded-full bg-white shadow-sm mb-2 group-hover:scale-110 transition-transform">
|
||||
{action.icon}
|
||||
|
||||
@@ -5,7 +5,11 @@ import { Button } from '@/components/ui/button';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
import { ArrowUpRight, ArrowDownRight, Clock } from 'lucide-react';
|
||||
|
||||
const TransactionsList: React.FC = () => {
|
||||
interface TransactionsListProps {
|
||||
onNavigate?: (section: string) => void;
|
||||
}
|
||||
|
||||
const TransactionsList: React.FC<TransactionsListProps> = ({ onNavigate }) => {
|
||||
const transactions = [
|
||||
{
|
||||
id: 1,
|
||||
@@ -70,8 +74,8 @@ const TransactionsList: React.FC = () => {
|
||||
<DirhamIcon className="w-3 h-3" /> {tx.amount}
|
||||
</div>
|
||||
<span className={`text-xs px-2 py-0.5 rounded-full ${tx.status === 'Successful'
|
||||
? 'bg-green-50 text-green-600'
|
||||
: 'bg-yellow-50 text-yellow-600'
|
||||
? 'bg-green-50 text-green-600'
|
||||
: 'bg-yellow-50 text-yellow-600'
|
||||
}`}>
|
||||
{tx.status}
|
||||
</span>
|
||||
@@ -80,7 +84,11 @@ const TransactionsList: React.FC = () => {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button variant="outline" className="w-full mt-6 text-slate-600 hover:text-purple-600 hover:border-purple-200">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full mt-6 text-slate-600 hover:text-purple-600 hover:border-purple-200"
|
||||
onClick={() => onNavigate?.('budget')}
|
||||
>
|
||||
View All Transactions
|
||||
</Button>
|
||||
</GlassCard>
|
||||
|
||||
@@ -12,6 +12,8 @@ import FullExpensesTable from '@/components/budget/FullExpensesTable';
|
||||
import AddExpenseForm from '@/components/budget/AddExpenseForm';
|
||||
import SetBudgetForm from '@/components/budget/SetBudgetForm';
|
||||
|
||||
import BudgetGoalPlanning from '@/components/budget/BudgetGoalPlanning'; // Import new component
|
||||
|
||||
export default function BudgetManager() {
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in text-slate-800 pb-10">
|
||||
@@ -43,6 +45,7 @@ export default function BudgetManager() {
|
||||
<TabsTrigger value="overview" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Overview</TabsTrigger>
|
||||
<TabsTrigger value="budgets" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Budgets</TabsTrigger>
|
||||
<TabsTrigger value="expenses" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Expenses</TabsTrigger>
|
||||
<TabsTrigger value="goal-planning" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Goal Planning</TabsTrigger>
|
||||
<TabsTrigger value="add-expense" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Add Expense</TabsTrigger>
|
||||
<TabsTrigger value="set-budget" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Set Budget</TabsTrigger>
|
||||
</TabsList>
|
||||
@@ -65,12 +68,17 @@ export default function BudgetManager() {
|
||||
<FullExpensesTable />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 4: Add Expense */}
|
||||
{/* Tab 4: Goal Planning */}
|
||||
<TabsContent value="goal-planning" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<BudgetGoalPlanning />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 5: Add Expense */}
|
||||
<TabsContent value="add-expense" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<AddExpenseForm />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 5: Set Budget */}
|
||||
{/* Tab 6: Set Budget */}
|
||||
<TabsContent value="set-budget" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<SetBudgetForm />
|
||||
</TabsContent>
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function SavingsBooster() {
|
||||
<TabsTrigger value="goals" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Goals</TabsTrigger>
|
||||
<TabsTrigger value="accounts" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Accounts</TabsTrigger>
|
||||
<TabsTrigger value="calculator" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Calculator</TabsTrigger>
|
||||
<TabsTrigger value="add-goal" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Add Goal</TabsTrigger>
|
||||
<TabsTrigger value="manual-goal" className="rounded-xl px-4 lg:px-6 py-2.5 text-sm font-medium data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm text-slate-500 transition-all">Manual Goal</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Tab 1: Overview */}
|
||||
@@ -61,18 +61,18 @@ export default function SavingsBooster() {
|
||||
<SavingsGoalsGrid />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 3: Accounts */}
|
||||
{/* Tab 4: Accounts */}
|
||||
<TabsContent value="accounts" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<SavingsAccountsTable />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 4: Calculator */}
|
||||
{/* Tab 5: Calculator */}
|
||||
<TabsContent value="calculator" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<CompoundInterestCalculator />
|
||||
</TabsContent>
|
||||
|
||||
{/* Tab 5: Add Goal */}
|
||||
<TabsContent value="add-goal" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
{/* Tab 6: Manual Goal (Renamed) */}
|
||||
<TabsContent value="manual-goal" className="outline-none animate-in fade-in slide-in-from-bottom-2 duration-500">
|
||||
<AddGoalForm />
|
||||
</TabsContent>
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface HoldingsTableProps {
|
||||
holdings: Array<{
|
||||
name: string;
|
||||
@@ -30,6 +32,10 @@ interface HoldingsTableProps {
|
||||
}
|
||||
|
||||
const HoldingsTable: React.FC<HoldingsTableProps> = ({ holdings }) => {
|
||||
const handleAction = (type: 'increase' | 'decrease', symbol: string) => {
|
||||
toast.success(`Position ${type === 'increase' ? 'Increased' : 'Reduced'}: ${symbol}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<GlassCard className="overflow-hidden">
|
||||
<Table>
|
||||
@@ -71,10 +77,20 @@ const HoldingsTable: React.FC<HoldingsTableProps> = ({ holdings }) => {
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Button size="icon" variant="outline" className="h-7 w-7 rounded-lg hover:bg-slate-100 text-slate-600">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
className="h-7 w-7 rounded-lg hover:bg-slate-100 text-slate-600"
|
||||
onClick={() => handleAction('decrease', holding.symbol)}
|
||||
>
|
||||
<Minus className="w-3 h-3" />
|
||||
</Button>
|
||||
<Button size="icon" variant="outline" className="h-7 w-7 rounded-lg hover:bg-slate-100 text-slate-600">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
className="h-7 w-7 rounded-lg hover:bg-slate-100 text-slate-600"
|
||||
onClick={() => handleAction('increase', holding.symbol)}
|
||||
>
|
||||
<Plus className="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -7,11 +7,28 @@ import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { DirhamIcon } from '@/components/ui/custom-icons';
|
||||
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface QuickInvestmentFormProps {
|
||||
holdings: Array<{ symbol: string; name: string }>;
|
||||
}
|
||||
|
||||
const QuickInvestmentForm: React.FC<QuickInvestmentFormProps> = ({ holdings }) => {
|
||||
const handleTrade = (type: 'buy' | 'sell') => {
|
||||
const symbol = (document.getElementById('holding') as HTMLButtonElement | null)?.innerText || 'Asset';
|
||||
const amount = (document.getElementById('amount') as HTMLInputElement | null)?.value;
|
||||
|
||||
if (!amount) {
|
||||
toast.error("Please enter an amount");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success(`Order Placed: ${type === 'buy' ? 'Bought' : 'Sold'} AED ${amount} of ${symbol}`);
|
||||
|
||||
// Reset form (simple dom reset for now)
|
||||
if (document.getElementById('amount')) (document.getElementById('amount') as HTMLInputElement).value = '';
|
||||
};
|
||||
|
||||
return (
|
||||
<GlassCard className="p-6 h-full flex flex-col justify-center">
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-6">Quick Investment</h3>
|
||||
@@ -41,10 +58,18 @@ const QuickInvestmentForm: React.FC<QuickInvestmentFormProps> = ({ holdings }) =
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 pt-4">
|
||||
<Button variant="outline" className="text-green-600 border-green-200 hover:bg-green-50 hover:text-green-700 w-full">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="text-green-600 border-green-200 hover:bg-green-50 hover:text-green-700 w-full"
|
||||
onClick={() => handleTrade('buy')}
|
||||
>
|
||||
Buy Asset
|
||||
</Button>
|
||||
<Button variant="outline" className="text-red-600 border-red-200 hover:bg-red-50 hover:text-red-700 w-full">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="text-red-600 border-red-200 hover:bg-red-50 hover:text-red-700 w-full"
|
||||
onClick={() => handleTrade('sell')}
|
||||
>
|
||||
Sell Asset
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -11,9 +11,28 @@ import { format } from 'date-fns';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Calendar as CalendarComponent } from '@/components/ui/calendar'; // Assuming shadcn Calendar exists
|
||||
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const AddGoalForm: React.FC = () => {
|
||||
const [date, setDate] = React.useState<Date>();
|
||||
|
||||
const handleCreateGoal = () => {
|
||||
const name = (document.getElementById('goal-name') as HTMLInputElement).value;
|
||||
const amount = (document.getElementById('target-amount') as HTMLInputElement).value;
|
||||
|
||||
if (!name || !amount) {
|
||||
toast.error("Please fill in goal name and target amount");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success(`Goal Created: ${name}`);
|
||||
|
||||
// Reset
|
||||
(document.getElementById('goal-name') as HTMLInputElement).value = '';
|
||||
(document.getElementById('target-amount') as HTMLInputElement).value = '';
|
||||
setDate(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center h-[450px]">
|
||||
<GlassCard className="p-8 w-full max-w-md">
|
||||
@@ -60,7 +79,10 @@ const AddGoalForm: React.FC = () => {
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<Button className="w-full mt-4 bg-slate-900 text-white hover:bg-slate-800 shadow-md">
|
||||
<Button
|
||||
className="w-full mt-4 bg-slate-900 text-white hover:bg-slate-800 shadow-md"
|
||||
onClick={handleCreateGoal}
|
||||
>
|
||||
Create Savings Goal
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,8 @@ import { Progress } from '@/components/ui/progress';
|
||||
import { Clock, Plus } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const SavingsGoalsGrid: React.FC = () => {
|
||||
|
||||
const goals = [
|
||||
@@ -18,6 +20,14 @@ const SavingsGoalsGrid: React.FC = () => {
|
||||
{ name: 'Home Downpayment', saved: 125000, target: 200000, daysLeft: 730, color: 'bg-indigo-500' },
|
||||
];
|
||||
|
||||
const handleAddSavings = (amount: number | 'custom', goalName: string) => {
|
||||
if (amount === 'custom') {
|
||||
toast.info("Custom amount feature pending: Enter amount manually soon.");
|
||||
} else {
|
||||
toast.success(`Ajded AED ${amount} to ${goalName}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{goals.map((goal, index) => {
|
||||
@@ -49,13 +59,27 @@ const SavingsGoalsGrid: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<Button variant="outline" size="sm" className="h-8 text-xs border-slate-200 hover:bg-slate-50 hover:text-slate-900">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 text-xs border-slate-200 hover:bg-slate-50 hover:text-slate-900"
|
||||
onClick={() => handleAddSavings(1000, goal.name)}
|
||||
>
|
||||
+1K
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" className="h-8 text-xs border-slate-200 hover:bg-slate-50 hover:text-slate-900">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-8 text-xs border-slate-200 hover:bg-slate-50 hover:text-slate-900"
|
||||
onClick={() => handleAddSavings(5000, goal.name)}
|
||||
>
|
||||
+5K
|
||||
</Button>
|
||||
<Button size="sm" className="h-8 text-xs bg-black text-white hover:bg-slate-800">
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-8 text-xs bg-black text-white hover:bg-slate-800"
|
||||
onClick={() => handleAddSavings('custom', goal.name)}
|
||||
>
|
||||
Custom
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
33
src/components/ui/HoverPreview.tsx
Normal file
33
src/components/ui/HoverPreview.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
||||
|
||||
interface HoverPreviewProps {
|
||||
trigger: ReactNode;
|
||||
content: ReactNode;
|
||||
openDelay?: number;
|
||||
side?: "top" | "right" | "bottom" | "left";
|
||||
align?: "start" | "center" | "end";
|
||||
}
|
||||
|
||||
export const HoverPreview: React.FC<HoverPreviewProps> = ({
|
||||
trigger,
|
||||
content,
|
||||
openDelay = 200,
|
||||
side = "bottom",
|
||||
align = "start"
|
||||
}) => {
|
||||
return (
|
||||
<HoverCard openDelay={openDelay}>
|
||||
<HoverCardTrigger asChild>
|
||||
{trigger}
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
side={side}
|
||||
align={align}
|
||||
className="w-80 p-4 bg-white/90 backdrop-blur-xl border border-white/20 rounded-2xl shadow-2xl z-50"
|
||||
>
|
||||
{content}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
);
|
||||
};
|
||||
@@ -42,7 +42,7 @@ const Index = () => {
|
||||
const renderContent = () => {
|
||||
switch (activeSection) {
|
||||
case 'dashboard':
|
||||
return <Dashboard />;
|
||||
return <Dashboard onNavigate={setActiveSection} />;
|
||||
|
||||
case 'analyze':
|
||||
return <WealthAssistant />;
|
||||
@@ -65,8 +65,7 @@ const Index = () => {
|
||||
case 'insurance':
|
||||
return <InsuranceAdvisor />;
|
||||
|
||||
case 'goals':
|
||||
return <GoalPlanning />;
|
||||
// Goals case removed as it is now part of BudgetManager
|
||||
|
||||
case 'advisor':
|
||||
return <Advisor />;
|
||||
|
||||
Reference in New Issue
Block a user