feat: Add FAB with asset price update, mobile optimizations, and localized financial trend

- Add Floating Action Button (FAB) with 3 quick actions
- Implement Asset Price Update dialog for bulk price updates
- Add bulk price update API endpoint with transaction support
- Optimize multiselect, calendar, and dropdown options for mobile (44px touch targets)
- Add custom date range popover to save space in Overview header
- Localize number format suffixes (k/m/b for EN, rb/jt/m for ID)
- Localize date format in Financial Trend (Oct 8 vs 8 Okt)
- Fix negative values in trend line chart (domain auto)
- Improve Asset Price Update dialog layout (compact horizontal)
- Add mobile-optimized calendar with responsive cells
- Fix FAB overlay and close button position
- Add translations for FAB and asset price updates
This commit is contained in:
dwindown
2025-10-12 23:30:54 +07:00
parent 46488a09e2
commit 49d60676d0
33 changed files with 1340 additions and 444 deletions

View File

@@ -231,11 +231,11 @@ export function TransactionDialog({ open, onOpenChange, transaction, walletId: i
</ResponsiveDialogDescription>
</ResponsiveDialogHeader>
<form onSubmit={handleSubmit}>
<div className="grid gap-4 py-4">
<div className="grid gap-4 p-4 md:py-4 md:px-0">
<div className="grid gap-2">
<Label htmlFor="wallet">{t.transactionDialog.wallet}</Label>
<Label htmlFor="wallet" className="text-base md:text-sm">{t.transactionDialog.wallet}</Label>
<Select value={walletId} onValueChange={setWalletId}>
<SelectTrigger>
<SelectTrigger className="h-11 md:h-9 text-base md:text-sm">
<SelectValue placeholder={t.transactionDialog.selectWallet} />
</SelectTrigger>
<SelectContent>
@@ -250,22 +250,22 @@ export function TransactionDialog({ open, onOpenChange, transaction, walletId: i
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="amount">{t.transactionDialog.amount}</Label>
<Label htmlFor="amount" className="text-base md:text-sm">{t.transactionDialog.amount}</Label>
<Input
id="amount"
type="number"
step="0.01"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder={t.transactionDialog.amountPlaceholder}
required
placeholder="0"
className="h-11 md:h-9 text-base md:text-sm"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="direction">{t.transactionDialog.direction}</Label>
<Label htmlFor="direction" className="text-base md:text-sm">{t.transactionDialog.direction}</Label>
<Select value={direction} onValueChange={(value: "in" | "out") => setDirection(value)}>
<SelectTrigger>
<SelectTrigger className="h-11 md:h-9 text-base md:text-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -277,31 +277,34 @@ export function TransactionDialog({ open, onOpenChange, transaction, walletId: i
</div>
<div className="grid gap-2">
<Label htmlFor="date">{t.transactionDialog.date}</Label>
<Label htmlFor="date" className="text-base md:text-sm">{t.transactionDialog.date}</Label>
<DatePicker
date={selectedDate}
onDateChange={(date) => date && setSelectedDate(date)}
placeholder={t.transactionDialog.selectDate}
className="h-11 md:h-9 text-base md:text-sm w-full"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="category">{t.transactionDialog.category}</Label>
<Label htmlFor="category" className="text-base md:text-sm">{t.transactionDialog.category}</Label>
<MultipleSelector
options={categoryOptions}
selected={categories}
onChange={setCategories}
placeholder={t.transactionDialog.categoryPlaceholder}
placeholder={t.transactionDialog.selectCategory}
className="min-h-[44px] md:min-h-[40px] text-base md:text-sm"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="memo">{t.transactionDialog.memo}</Label>
<Label htmlFor="memo" className="text-base md:text-sm">{t.transactionDialog.memo}</Label>
<Input
id="memo"
value={memo}
onChange={(e) => setMemo(e.target.value)}
placeholder={t.transactionDialog.memoPlaceholder}
placeholder={t.transactionDialog.addMemo}
className="h-11 md:h-9 text-base md:text-sm"
/>
</div>
@@ -312,10 +315,10 @@ export function TransactionDialog({ open, onOpenChange, transaction, walletId: i
)}
</div>
<ResponsiveDialogFooter>
<Button type="button" variant="outline" onClick={() => handleOpenChange(false)}>
<Button type="button" variant="outline" onClick={() => handleOpenChange(false)} className="h-11 md:h-9 text-base md:text-sm">
{t.common.cancel}
</Button>
<Button type="submit" disabled={loading}>
<Button type="submit" disabled={loading} className="h-11 md:h-9 text-base md:text-sm">
{loading ? t.common.loading : isEditing ? t.common.save : t.common.add}
</Button>
</ResponsiveDialogFooter>