feat: Implement multi-language system (ID/EN) for member dashboard
- Create translation files (locales/id.ts, locales/en.ts) - Add LanguageContext with useLanguage hook - Add LanguageToggle component in sidebar - Default language: Indonesian (ID) - Translate WalletDialog and TransactionDialog - Language preference persisted in localStorage - Type-safe translations with autocomplete Next: Translate remaining pages (Overview, Wallets, Transactions, Profile)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect } from "react"
|
||||
import { toast } from "sonner"
|
||||
import { useLanguage } from "@/contexts/LanguageContext"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
@@ -45,17 +46,19 @@ interface TransactionDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
transaction?: Transaction | null
|
||||
walletId?: string | null
|
||||
onSuccess: () => void
|
||||
}
|
||||
|
||||
const API = "/api"
|
||||
|
||||
export function TransactionDialog({ open, onOpenChange, transaction, onSuccess }: TransactionDialogProps) {
|
||||
export function TransactionDialog({ open, onOpenChange, transaction, walletId: initialWalletId, onSuccess }: TransactionDialogProps) {
|
||||
const { t } = useLanguage()
|
||||
const [wallets, setWallets] = useState<Wallet[]>([])
|
||||
const [categoryOptions, setCategoryOptions] = useState<Option[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [amount, setAmount] = useState(transaction?.amount?.toString() || "")
|
||||
const [walletId, setWalletId] = useState(transaction?.walletId || "")
|
||||
const [walletId, setWalletId] = useState(transaction?.walletId || initialWalletId || "")
|
||||
const [direction, setDirection] = useState<"in" | "out">(transaction?.direction || "out")
|
||||
const [categories, setCategories] = useState<string[]>(
|
||||
transaction?.category ? transaction.category.split(',').map(c => c.trim()) : []
|
||||
@@ -222,18 +225,18 @@ export function TransactionDialog({ open, onOpenChange, transaction, onSuccess }
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{isEditing ? "Edit Transaction" : "Add New Transaction"}</DialogTitle>
|
||||
<DialogTitle>{isEditing ? t.transactionDialog.editTitle : t.transactionDialog.addTitle}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{isEditing ? "Update your transaction details." : "Record a new transaction."}
|
||||
{isEditing ? t.transactionDialog.editTitle : t.transactionDialog.addTitle}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="wallet">Wallet</Label>
|
||||
<Label htmlFor="wallet">{t.transactionDialog.wallet}</Label>
|
||||
<Select value={walletId} onValueChange={setWalletId}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a wallet" />
|
||||
<SelectValue placeholder={t.transactionDialog.selectWallet} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{wallets.map(wallet => (
|
||||
@@ -247,58 +250,58 @@ export function TransactionDialog({ open, onOpenChange, transaction, onSuccess }
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="amount">Amount</Label>
|
||||
<Label htmlFor="amount">{t.transactionDialog.amount}</Label>
|
||||
<Input
|
||||
id="amount"
|
||||
type="number"
|
||||
step="0.01"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
placeholder="0.00"
|
||||
placeholder={t.transactionDialog.amountPlaceholder}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="direction">Type</Label>
|
||||
<Label htmlFor="direction">{t.transactionDialog.direction}</Label>
|
||||
<Select value={direction} onValueChange={(value: "in" | "out") => setDirection(value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="in">Income</SelectItem>
|
||||
<SelectItem value="out">Expense</SelectItem>
|
||||
<SelectItem value="in">{t.transactionDialog.income}</SelectItem>
|
||||
<SelectItem value="out">{t.transactionDialog.expense}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="date">Date</Label>
|
||||
<Label htmlFor="date">{t.transactionDialog.date}</Label>
|
||||
<DatePicker
|
||||
date={selectedDate}
|
||||
onDateChange={(date) => date && setSelectedDate(date)}
|
||||
placeholder="Select date"
|
||||
placeholder={t.transactionDialog.selectDate}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="category">Categories</Label>
|
||||
<Label htmlFor="category">{t.transactionDialog.category}</Label>
|
||||
<MultipleSelector
|
||||
options={categoryOptions}
|
||||
selected={categories}
|
||||
onChange={setCategories}
|
||||
placeholder="Select or create categories..."
|
||||
placeholder={t.transactionDialog.categoryPlaceholder}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="memo">Memo</Label>
|
||||
<Label htmlFor="memo">{t.transactionDialog.memo}</Label>
|
||||
<Input
|
||||
id="memo"
|
||||
value={memo}
|
||||
onChange={(e) => setMemo(e.target.value)}
|
||||
placeholder="Optional description"
|
||||
placeholder={t.transactionDialog.memoPlaceholder}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -310,10 +313,10 @@ export function TransactionDialog({ open, onOpenChange, transaction, onSuccess }
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => handleOpenChange(false)}>
|
||||
Cancel
|
||||
{t.common.cancel}
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? "Saving..." : isEditing ? "Update" : "Create"}
|
||||
{loading ? t.common.loading : isEditing ? t.common.save : t.common.add}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user