Skip to content
Merged
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
6 changes: 4 additions & 2 deletions frontend/src/components/BudgetSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Input } from "./ui/Input";
import { Button } from "./ui/Button";
import { ProgressBar } from "./ui/ProgressBar";
import { Modal } from "./ui/Modal";
import { useCurrency } from "../context/CurrencyContext";

interface BudgetSectionProps {
monthId: number;
Expand All @@ -22,6 +23,7 @@ export function BudgetSection({
isReadOnly,
onUpdate,
}: BudgetSectionProps) {
const { formatCurrency } = useCurrency();
const [isManaging, setIsManaging] = useState(false);
const [isAddingCategory, setIsAddingCategory] = useState(false);
const [editingCategoryId, setEditingCategoryId] = useState<number | null>(null);
Expand Down Expand Up @@ -131,7 +133,7 @@ export function BudgetSection({
</span>
<div className="flex items-center gap-2">
<span className="text-xs text-charcoal-500 dark:text-charcoal-400">
${budget.spent_amount.toFixed(2)} / ${budget.allocated_amount.toFixed(2)}
{formatCurrency(budget.spent_amount)} / {formatCurrency(budget.allocated_amount)}
</span>
{!isReadOnly && (
<button
Expand Down Expand Up @@ -198,7 +200,7 @@ export function BudgetSection({
<span className="text-sm">{cat.label}</span>
<div className="flex items-center gap-2">
<span className="text-xs text-charcoal-500">
${cat.default_amount.toFixed(2)}
{formatCurrency(cat.default_amount)}
</span>
<button
onClick={() => startEditCategory(cat)}
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/FixedExpenses.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { Card } from "./ui/Card";
import { Input } from "./ui/Input";
import { Button } from "./ui/Button";
import { Modal } from "./ui/Modal";
import { useCurrency } from "../context/CurrencyContext";

interface FixedExpensesProps {
expenses: FixedExpense[];
onUpdate: () => void;
}

export function FixedExpenses({ expenses, onUpdate }: FixedExpensesProps) {
const { formatCurrency } = useCurrency();
const [isManaging, setIsManaging] = useState(false);
const [isAdding, setIsAdding] = useState(false);
const [editingId, setEditingId] = useState<number | null>(null);
Expand Down Expand Up @@ -81,7 +83,7 @@ export function FixedExpenses({ expenses, onUpdate }: FixedExpensesProps) {
{expense.label}
</span>
<span className="text-sm text-charcoal-600 dark:text-charcoal-400">
${expense.amount.toFixed(2)}
{formatCurrency(expense.amount)}
</span>
</div>
))}
Expand All @@ -98,7 +100,7 @@ export function FixedExpenses({ expenses, onUpdate }: FixedExpensesProps) {
Total
</span>
<span className="text-sm font-semibold text-charcoal-800 dark:text-sand-100">
${total.toFixed(2)}
{formatCurrency(total)}
</span>
</div>
)}
Expand Down Expand Up @@ -142,7 +144,7 @@ export function FixedExpenses({ expenses, onUpdate }: FixedExpensesProps) {
<div className="flex items-center justify-between py-2 border-b border-sand-200 dark:border-charcoal-800">
<span className="text-sm">{expense.label}</span>
<div className="flex items-center gap-2">
<span className="text-sm">${expense.amount.toFixed(2)}</span>
<span className="text-sm">{formatCurrency(expense.amount)}</span>
<button
onClick={() => startEdit(expense)}
className="p-1 hover:bg-sand-200 dark:hover:bg-charcoal-800"
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/IncomeSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IncomeEntry, api } from "../api/client";
import { Card } from "./ui/Card";
import { Input } from "./ui/Input";
import { Button } from "./ui/Button";
import { useCurrency } from "../context/CurrencyContext";

interface IncomeSectionProps {
monthId: number;
Expand All @@ -13,6 +14,7 @@ interface IncomeSectionProps {
}

export function IncomeSection({ monthId, entries, isReadOnly, onUpdate }: IncomeSectionProps) {
const { formatCurrency } = useCurrency();
const [isAdding, setIsAdding] = useState(false);
const [editingId, setEditingId] = useState<number | null>(null);
const [label, setLabel] = useState("");
Expand Down Expand Up @@ -110,7 +112,7 @@ export function IncomeSection({ monthId, entries, isReadOnly, onUpdate }: Income
</span>
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-sage-600 dark:text-sage-400">
${entry.amount.toFixed(2)}
{formatCurrency(entry.amount)}
</span>
{!isReadOnly && (
<>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/ItemsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Card } from "./ui/Card";
import { Input } from "./ui/Input";
import { Select } from "./ui/Select";
import { Button } from "./ui/Button";
import { useCurrency } from "../context/CurrencyContext";

interface ItemsSectionProps {
monthId: number;
Expand All @@ -21,6 +22,7 @@ export function ItemsSection({
isReadOnly,
onUpdate,
}: ItemsSectionProps) {
const { formatCurrency } = useCurrency();
const [isAdding, setIsAdding] = useState(false);
const [editingId, setEditingId] = useState<number | null>(null);
const [description, setDescription] = useState("");
Expand Down Expand Up @@ -243,7 +245,7 @@ export function ItemsSection({
</span>
</td>
<td className="py-2 text-right font-medium text-terracotta-600 dark:text-terracotta-400">
${item.amount.toFixed(2)}
{formatCurrency(item.amount)}
</td>
{!isReadOnly && (
<td className="py-2">
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/ProjectedSavingsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TrendingUp, HelpCircle } from "lucide-react";
import { Card } from "./ui/Card";
import { useCurrency } from "../context/CurrencyContext";

interface ProjectedSavingsCardProps {
savings: number;
Expand All @@ -8,6 +9,7 @@ interface ProjectedSavingsCardProps {
}

export function ProjectedSavingsCard({ savings, remaining, onAnalyzeClick }: ProjectedSavingsCardProps) {
const { formatCurrency } = useCurrency();
const projected = savings + remaining;

return (
Expand All @@ -20,7 +22,7 @@ export function ProjectedSavingsCard({ savings, remaining, onAnalyzeClick }: Pro
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-semibold text-sage-700 dark:text-sage-400">
${projected.toFixed(2)}
{formatCurrency(projected)}
</span>
{onAnalyzeClick && (
<button
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/RetirementSavingsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { TrendingUp, Pencil, Check, X } from "lucide-react";
import { api } from "../api/client";
import { Card } from "./ui/Card";
import { Input } from "./ui/Input";
import { useCurrency } from "../context/CurrencyContext";

export function RetirementSavingsCard() {
const { formatCurrency } = useCurrency();
const [amount, setAmount] = useState<number>(0);
const [isEditing, setIsEditing] = useState(false);
const [editValue, setEditValue] = useState("");
Expand Down Expand Up @@ -63,7 +65,7 @@ export function RetirementSavingsCard() {
) : (
<div className="flex items-center gap-2">
<span className="text-xl font-semibold text-sage-600 dark:text-sage-400">
${amount.toFixed(2)}
{formatCurrency(amount)}
</span>
<button
onClick={startEdit}
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/SavingsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { Input } from "./ui/Input";
import { ProgressBar } from "./ui/ProgressBar";
import { Modal } from "./ui/Modal";
import { Button } from "./ui/Button";
import { useCurrency } from "../context/CurrencyContext";

interface SavingsCardProps {
onSavingsChange?: (savings: number) => void;
remaining: number;
}

export function SavingsCard({ onSavingsChange, remaining }: SavingsCardProps) {
const { formatCurrency } = useCurrency();
const [savings, setSavings] = useState<number>(0);
const [savingsGoal, setSavingsGoal] = useState<number>(0);
const [isEditing, setIsEditing] = useState(false);
Expand Down Expand Up @@ -115,7 +117,7 @@ export function SavingsCard({ onSavingsChange, remaining }: SavingsCardProps) {
) : (
<div className="flex items-center justify-between mb-3">
<span className="text-lg sm:text-xl font-semibold text-sage-700 dark:text-sage-400">
${savings.toFixed(2)}
{formatCurrency(savings)}
</span>
<button
onClick={startEdit}
Expand Down Expand Up @@ -153,7 +155,7 @@ export function SavingsCard({ onSavingsChange, remaining }: SavingsCardProps) {
) : (
<div className="flex items-center justify-between text-xs">
<span className="text-charcoal-500 dark:text-charcoal-400">
Goal: ${target.toFixed(2)}
Goal: {formatCurrency(target)}
</span>
<button
onClick={startEditGoal}
Expand All @@ -171,7 +173,7 @@ export function SavingsCard({ onSavingsChange, remaining }: SavingsCardProps) {
{isAhead ? '✓' : '⚠️'} {Math.abs(percentage - 100).toFixed(1)}% {isAhead ? 'ahead' : 'behind'}
</span>
<span className="text-charcoal-500 dark:text-charcoal-400">
{isAhead ? '+' : ''}{difference.toFixed(2)}
{isAhead ? '+' : ''}{formatCurrency(difference, { showSymbol: false })}
</span>
</div>

Expand Down
10 changes: 6 additions & 4 deletions frontend/src/components/Stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import {
import { api, StatsResponse } from "../api/client";
import { Modal } from "./ui/Modal";
import { Button } from "./ui/Button";
import { useCurrency } from "../context/CurrencyContext";

const MONTH_NAMES = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
];

export function Stats() {
const { formatCurrency, getCurrencySymbol } = useCurrency();
const [isOpen, setIsOpen] = useState(false);
const [stats, setStats] = useState<StatsResponse | null>(null);
const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -67,15 +69,15 @@ export function Stats() {
Avg Monthly Spending
</div>
<div className="text-lg font-semibold text-terracotta-600 dark:text-terracotta-400">
${stats.average_monthly_spending.toFixed(2)}
{formatCurrency(stats.average_monthly_spending)}
</div>
</div>
<div className="p-4 bg-sand-100 dark:bg-charcoal-800">
<div className="text-xs text-charcoal-500 dark:text-charcoal-400 mb-1">
Avg Monthly Income
</div>
<div className="text-lg font-semibold text-sage-600 dark:text-sage-400">
${stats.average_monthly_income.toFixed(2)}
{formatCurrency(stats.average_monthly_income)}
</div>
</div>
</div>
Expand Down Expand Up @@ -143,7 +145,7 @@ export function Stats() {
</span>
<div className="flex items-center gap-3">
<span className="text-sm text-charcoal-600 dark:text-charcoal-400">
${cat.current_month_spent.toFixed(2)}
{formatCurrency(cat.current_month_spent)}
</span>
{cat.change_amount !== 0 && (
<div
Expand All @@ -160,7 +162,7 @@ export function Stats() {
)}
{cat.change_percent !== null
? `${Math.abs(cat.change_percent).toFixed(0)}%`
: `$${Math.abs(cat.change_amount).toFixed(0)}`}
: `${getCurrencySymbol()}${Math.abs(cat.change_amount).toFixed(0)}`}
</div>
)}
</div>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/Summary.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { TrendingDown, Wallet, CreditCard, PiggyBank } from "lucide-react";
import { Card } from "./ui/Card";
import { ReactNode } from "react";
import { useCurrency } from "../context/CurrencyContext";

interface SummaryProps {
totalIncome: number;
Expand All @@ -11,6 +12,7 @@ interface SummaryProps {
}

export function Summary({ totalIncome, totalFixed, totalSpent, remaining, extraCard }: SummaryProps) {
const { formatCurrency } = useCurrency();
const isPositive = remaining >= 0;

const items = [
Expand Down Expand Up @@ -53,7 +55,7 @@ export function Summary({ totalIncome, totalFixed, totalSpent, remaining, extraC
{item.label}
</div>
<div className={`text-lg sm:text-xl font-semibold ${item.color}`}>
${Math.abs(item.value).toFixed(2)}
{formatCurrency(item.value, { absolute: true })}
{item.label === "Remaining" && item.value < 0 && (
<span className="text-xs ml-1">deficit</span>
)}
Expand Down
26 changes: 14 additions & 12 deletions frontend/src/components/VarianceModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Modal } from "./ui/Modal";
import { MonthlyBudgetWithCategory } from "../api/client";
import { TrendingUp, TrendingDown, AlertCircle, PartyPopper } from "lucide-react";
import { useCurrency } from "../context/CurrencyContext";

interface VarianceModalProps {
isOpen: boolean;
Expand All @@ -27,6 +28,7 @@ export function VarianceModal({
totalFixed,
totalBudgeted,
}: VarianceModalProps) {
const { formatCurrency } = useCurrency();
const overBudget: BudgetVariance[] = [];
const underBudget: BudgetVariance[] = [];
const unplanned: BudgetVariance[] = [];
Expand Down Expand Up @@ -76,7 +78,7 @@ export function VarianceModal({
</p>
{netVariance < 0 && (
<p className="text-sm text-sage-600 dark:text-sage-500">
You've saved ${Math.abs(netVariance).toFixed(2)} more than planned across your categories.
You've saved {formatCurrency(Math.abs(netVariance))} more than planned across your categories.
</p>
)}
{underBudget.length > 0 && (
Expand All @@ -91,7 +93,7 @@ export function VarianceModal({
<AlertCircle className="text-terracotta-600 shrink-0" size={24} />
<div>
<p className="font-semibold text-terracotta-700 dark:text-terracotta-400">
You're ${(totalOverspend + totalUnplanned + incomeShortfall).toFixed(2)} over budget
You're {formatCurrency(totalOverspend + totalUnplanned + incomeShortfall)} over budget
</p>
<p className="text-sm text-terracotta-600 dark:text-terracotta-500">
Here's what's affecting your projected savings:
Expand All @@ -115,10 +117,10 @@ export function VarianceModal({
<span className="text-charcoal-700 dark:text-charcoal-300">{item.label}</span>
<div className="text-right">
<span className="text-terracotta-600 dark:text-terracotta-400 font-medium">
+${item.variance.toFixed(2)}
+{formatCurrency(item.variance)}
</span>
<span className="text-charcoal-500 dark:text-charcoal-500 text-xs ml-2">
(${item.spent.toFixed(2)} / ${item.allocated.toFixed(2)})
({formatCurrency(item.spent)} / {formatCurrency(item.allocated)})
</span>
</div>
</div>
Expand All @@ -141,7 +143,7 @@ export function VarianceModal({
>
<span className="text-charcoal-700 dark:text-charcoal-300">{item.label}</span>
<span className="text-amber-600 dark:text-amber-400 font-medium">
${item.spent.toFixed(2)}
{formatCurrency(item.spent)}
</span>
</div>
))}
Expand All @@ -157,10 +159,10 @@ export function VarianceModal({
</h3>
<div className="p-2 bg-terracotta-50 dark:bg-terracotta-900/20 rounded text-sm">
<p className="text-charcoal-700 dark:text-charcoal-300">
Income is <span className="font-medium text-terracotta-600 dark:text-terracotta-400">${incomeShortfall.toFixed(2)}</span> less than needed to cover expenses
Income is <span className="font-medium text-terracotta-600 dark:text-terracotta-400">{formatCurrency(incomeShortfall)}</span> less than needed to cover expenses
</p>
<p className="text-xs text-charcoal-500 mt-1">
Income: ${totalIncome.toFixed(2)} | Needed: ${incomeNeeded.toFixed(2)}
Income: {formatCurrency(totalIncome)} | Needed: {formatCurrency(incomeNeeded)}
</p>
</div>
</div>
Expand All @@ -181,10 +183,10 @@ export function VarianceModal({
<span className="text-charcoal-700 dark:text-charcoal-300">{item.label}</span>
<div className="text-right">
<span className="text-sage-600 dark:text-sage-400 font-medium">
-${Math.abs(item.variance).toFixed(2)}
-{formatCurrency(Math.abs(item.variance))}
</span>
<span className="text-charcoal-500 dark:text-charcoal-500 text-xs ml-2">
(${item.spent.toFixed(2)} / ${item.allocated.toFixed(2)})
({formatCurrency(item.spent)} / {formatCurrency(item.allocated)})
</span>
</div>
</div>
Expand All @@ -202,19 +204,19 @@ export function VarianceModal({
<div className="flex justify-between text-sm">
<span className="text-charcoal-600 dark:text-charcoal-400">Total over budget:</span>
<span className="text-terracotta-600 dark:text-terracotta-400 font-medium">
+${(totalOverspend + totalUnplanned).toFixed(2)}
+{formatCurrency(totalOverspend + totalUnplanned)}
</span>
</div>
<div className="flex justify-between text-sm mt-1">
<span className="text-charcoal-600 dark:text-charcoal-400">Total under budget:</span>
<span className="text-sage-600 dark:text-sage-400 font-medium">
-${totalSaved.toFixed(2)}
-{formatCurrency(totalSaved)}
</span>
</div>
<div className="flex justify-between text-sm mt-2 pt-2 border-t border-sand-200 dark:border-charcoal-800">
<span className="font-medium text-charcoal-700 dark:text-charcoal-300">Net impact:</span>
<span className={`font-semibold ${netVariance > 0 ? "text-terracotta-600 dark:text-terracotta-400" : "text-sage-600 dark:text-sage-400"}`}>
{netVariance > 0 ? "+" : "-"}${Math.abs(netVariance).toFixed(2)}
{netVariance > 0 ? "+" : "-"}{formatCurrency(Math.abs(netVariance))}
</span>
</div>
</div>
Expand Down
Loading