feat: better exchange rate tool ux

This commit is contained in:
Vasily Zubarev
2025-04-09 13:48:23 +02:00
parent 6ebc85c9df
commit d53044f3f6
3 changed files with 50 additions and 25 deletions

View File

@@ -0,0 +1,13 @@
import { Bot } from "lucide-react"
export default function AgentWindow({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className="border-2 border-purple-500 bg-purple-100 rounded-md overflow-hidden">
<div className="flex flex-row gap-1 items-center font-bold text-xs bg-purple-200 text-purple-800 p-1">
<Bot className="w-4 h-4" />
<span>Agent called: {title}</span>
</div>
<div className="p-4">{children}</div>
</div>
)
}

View File

@@ -16,9 +16,14 @@ export const FormConvertCurrency = ({
date?: Date | undefined date?: Date | undefined
onChange?: (value: number) => void onChange?: (value: number) => void
}) => { }) => {
if (!originalTotal || !originalCurrencyCode || !targetCurrencyCode || originalCurrencyCode === targetCurrencyCode) {
return <></>
}
const normalizedDate = startOfDay(date || new Date(Date.now() - 24 * 60 * 60 * 1000)) const normalizedDate = startOfDay(date || new Date(Date.now() - 24 * 60 * 60 * 1000))
const normalizedDateString = format(normalizedDate, "yyyy-MM-dd") const normalizedDateString = format(normalizedDate, "yyyy-MM-dd")
const [exchangeRate, setExchangeRate] = useState(0) const [exchangeRate, setExchangeRate] = useState(0)
const [convertedTotal, setConvertedTotal] = useState(0)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
useEffect(() => { useEffect(() => {
@@ -27,11 +32,11 @@ export const FormConvertCurrency = ({
setIsLoading(true) setIsLoading(true)
const exchangeRate = await getCurrencyRate(originalCurrencyCode, targetCurrencyCode, normalizedDate) const exchangeRate = await getCurrencyRate(originalCurrencyCode, targetCurrencyCode, normalizedDate)
setExchangeRate(exchangeRate) setExchangeRate(exchangeRate)
onChange?.(originalTotal * exchangeRate) setConvertedTotal(Math.round(originalTotal * exchangeRate * 100) / 100)
} catch (error) { } catch (error) {
console.error("Error fetching currency rates:", error) console.error("Error fetching currency rates:", error)
setExchangeRate(0) setExchangeRate(0)
onChange?.(0) setConvertedTotal(0)
} finally { } finally {
setIsLoading(false) setIsLoading(false)
} }
@@ -40,18 +45,19 @@ export const FormConvertCurrency = ({
fetchData() fetchData()
}, [originalCurrencyCode, targetCurrencyCode, normalizedDateString, originalTotal]) }, [originalCurrencyCode, targetCurrencyCode, normalizedDateString, originalTotal])
if (!originalTotal || !originalCurrencyCode || !targetCurrencyCode || originalCurrencyCode === targetCurrencyCode) { useEffect(() => {
return <></> onChange?.(convertedTotal)
} }, [convertedTotal])
return ( return (
<div className="flex flex-row gap-2 items-center text-muted-foreground"> <div className="flex flex-row gap-2 items-center">
{isLoading ? ( {isLoading ? (
<div className="flex flex-row gap-2"> <div className="flex flex-row items-center gap-2 text-sm text-muted-foreground">
<Loader2 className="animate-spin" /> <Loader2 className="w-4 h-4 animate-spin" />
<div>Loading exchange rates...</div> <div className="font-semibold">Loading exchange rates...</div>
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div>{formatCurrency(originalTotal * 100, originalCurrencyCode)}</div> <div>{formatCurrency(originalTotal * 100, originalCurrencyCode)}</div>
<div>=</div> <div>=</div>
@@ -60,11 +66,15 @@ export const FormConvertCurrency = ({
type="number" type="number"
step="0.01" step="0.01"
name="convertedTotal" name="convertedTotal"
value={(originalTotal * exchangeRate).toFixed(2)} value={convertedTotal}
onChange={(e) => onChange?.(parseFloat(e.target.value))} onChange={(e) => {
className="w-32 rounded-md border border-input bg-transparent px-1" const newValue = parseFloat(e.target.value || "0")
!isNaN(newValue) && setConvertedTotal(Math.round(newValue * 100) / 100)
}}
className="w-32 rounded-md border border-input px-2 py-1"
/> />
<div className="text-xs">(exchange rate on {format(normalizedDate, "LLLL dd, yyyy")})</div> </div>
<div className="text-xs text-muted-foreground">The exchange rate will be added to the transaction</div>
</div> </div>
)} )}
</div> </div>

View File

@@ -11,8 +11,10 @@ import { FormSelectType } from "@/components/forms/select-type"
import { FormInput, FormTextarea } from "@/components/forms/simple" import { FormInput, FormTextarea } from "@/components/forms/simple"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Category, Currency, Field, File, Project } from "@prisma/client" import { Category, Currency, Field, File, Project } from "@prisma/client"
import { format } from "date-fns"
import { Brain, Loader2 } from "lucide-react" import { Brain, Loader2 } from "lucide-react"
import { startTransition, useActionState, useMemo, useState } from "react" import { startTransition, useActionState, useMemo, useState } from "react"
import AgentWindow from "../agents/agent-window"
export default function AnalyzeForm({ export default function AnalyzeForm({
file, file,
@@ -197,16 +199,16 @@ export default function AnalyzeForm({
</div> </div>
{formData.total != 0 && formData.currencyCode && formData.currencyCode !== settings.default_currency && ( {formData.total != 0 && formData.currencyCode && formData.currencyCode !== settings.default_currency && (
<> <AgentWindow title={`Exchange rate on ${format(new Date(formData.issuedAt || Date.now()), "LLLL dd, yyyy")}`}>
<FormConvertCurrency <FormConvertCurrency
originalTotal={formData.total} originalTotal={formData.total}
originalCurrencyCode={formData.currencyCode} originalCurrencyCode={formData.currencyCode}
targetCurrencyCode={settings.default_currency} targetCurrencyCode={settings.default_currency}
date={formData.issuedAt ? new Date(formData.issuedAt) : undefined} date={new Date(formData.issuedAt || Date.now())}
onChange={(value) => setFormData((prev) => ({ ...prev, convertedTotal: value }))} onChange={(value) => setFormData((prev) => ({ ...prev, convertedTotal: value }))}
/> />
<input type="hidden" name="convertedCurrencyCode" value={settings.default_currency} /> <input type="hidden" name="convertedCurrencyCode" value={settings.default_currency} />
</> </AgentWindow>
)} )}
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-4">