feat(ui): değer grafiği, net varlık kartı, dashboard kartları ve ödeme diyaloğunda varlık bağı
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { getAssetOptionsAction, type AssetOption } from "@/features/assets/actions";
|
||||
import { getTransactionFormOptionsAction } from "@/features/transactions/actions";
|
||||
import { AccountSelect } from "@/features/transactions/components/transaction-form";
|
||||
import type { TransactionFormOptions } from "@/features/transactions/service";
|
||||
@@ -79,6 +81,21 @@ export function useAccounts() {
|
||||
return options?.accounts ?? null;
|
||||
}
|
||||
|
||||
/** Kaydın varlık türündeki birikim varlıkları (ödeme diyaloğu açıldığında). */
|
||||
function useAssetOptions(assetKey: AssetKey) {
|
||||
const [options, setOptions] = useState<AssetOption[] | null>(null);
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
getAssetOptionsAction().then((r) => {
|
||||
if (!cancelled && r.ok) setOptions(r.data.filter((a) => a.assetKey === assetKey));
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [assetKey]);
|
||||
return options;
|
||||
}
|
||||
|
||||
function Stat({ label, value, tone }: { label: string; value: string; tone?: string }) {
|
||||
return (
|
||||
<div className="min-w-0">
|
||||
@@ -602,16 +619,23 @@ function PaymentForm({ item, onClose }: { item: InKindView; onClose: () => void
|
||||
});
|
||||
const [errors, setErrors] = useState<Errors>({});
|
||||
const [busy, setBusy] = useState(false);
|
||||
const assets = useAssetOptions(item.assetKey);
|
||||
const [link, setLink] = useState(false);
|
||||
const [assetId, setAssetId] = useState("");
|
||||
const linked = method === "asset" && link;
|
||||
const linkAssetId = assetId || assets?.[0]?.id || "";
|
||||
|
||||
async function submit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setBusy(true);
|
||||
const amount = linked ? "" : v.amount;
|
||||
const r = await recordPaymentAction(item.id, {
|
||||
method,
|
||||
date: v.date,
|
||||
quantity: method === "cash" ? "" : v.quantity,
|
||||
amount: v.amount,
|
||||
accountId: v.amount ? v.accountId || accounts?.[0]?.id || "" : "",
|
||||
amount,
|
||||
accountId: amount ? v.accountId || accounts?.[0]?.id || "" : "",
|
||||
assetId: linked ? linkAssetId : "",
|
||||
note: v.note,
|
||||
});
|
||||
setBusy(false);
|
||||
@@ -659,17 +683,19 @@ function PaymentForm({ item, onClose }: { item: InKindView; onClose: () => void
|
||||
{errors.quantity ? <FieldError>{errors.quantity}</FieldError> : null}
|
||||
</Field>
|
||||
) : null}
|
||||
<Field data-invalid={Boolean(errors.amount)}>
|
||||
<FieldLabel htmlFor="pay-amount">
|
||||
{method === "cash" ? f.amount : f.assetAmount}
|
||||
</FieldLabel>
|
||||
<MoneyInput
|
||||
id="pay-amount"
|
||||
value={v.amount}
|
||||
onChange={(x) => setV((s) => ({ ...s, amount: x }))}
|
||||
/>
|
||||
{errors.amount ? <FieldError>{errors.amount}</FieldError> : null}
|
||||
</Field>
|
||||
{linked || (method === "asset" && !debt) ? null : (
|
||||
<Field data-invalid={Boolean(errors.amount)}>
|
||||
<FieldLabel htmlFor="pay-amount">
|
||||
{method === "cash" ? f.amount : f.assetAmount}
|
||||
</FieldLabel>
|
||||
<MoneyInput
|
||||
id="pay-amount"
|
||||
value={v.amount}
|
||||
onChange={(x) => setV((s) => ({ ...s, amount: x }))}
|
||||
/>
|
||||
{errors.amount ? <FieldError>{errors.amount}</FieldError> : null}
|
||||
</Field>
|
||||
)}
|
||||
<Field>
|
||||
<FieldLabel htmlFor="pay-date">{tr.transactions.fields.date}</FieldLabel>
|
||||
<Input
|
||||
@@ -684,7 +710,49 @@ function PaymentForm({ item, onClose }: { item: InKindView; onClose: () => void
|
||||
{method === "cash" ? (
|
||||
<FieldDescription className="-mt-2">{f.quantityHint}</FieldDescription>
|
||||
) : null}
|
||||
{method === "cash" || v.amount ? (
|
||||
{method === "asset" ? (
|
||||
<div className="flex flex-col gap-2 rounded-lg border p-3">
|
||||
<label className="flex items-start gap-2 text-sm">
|
||||
<Checkbox
|
||||
checked={link}
|
||||
onCheckedChange={(x) => setLink(x === true)}
|
||||
disabled={!assets?.length}
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<span>
|
||||
{debt ? t.link.debt : t.link.receivable}
|
||||
<span className="block text-xs text-muted-foreground">
|
||||
{assets && !assets.length
|
||||
? t.link.none
|
||||
: debt
|
||||
? t.link.debtHint
|
||||
: t.link.receivableHint}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
{link && assets?.length ? (
|
||||
<Field data-invalid={Boolean(errors.assetId)}>
|
||||
<FieldLabel htmlFor="pay-asset" className="sr-only">
|
||||
{t.link.asset}
|
||||
</FieldLabel>
|
||||
<Select value={linkAssetId} onValueChange={setAssetId}>
|
||||
<SelectTrigger id="pay-asset" className="w-full">
|
||||
<SelectValue>{assets.find((a) => a.id === linkAssetId)?.name}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{assets.map((a) => (
|
||||
<SelectItem key={a.id} value={a.id}>
|
||||
{a.name} · {formatAssetQuantity(item.assetKey, a.quantity)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{errors.assetId ? <FieldError>{errors.assetId}</FieldError> : null}
|
||||
</Field>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{(method === "cash" || v.amount) && !linked ? (
|
||||
<Field data-invalid={Boolean(errors.accountId)}>
|
||||
<FieldLabel htmlFor="pay-account">{f.account}</FieldLabel>
|
||||
<AccountSelect
|
||||
|
||||
Reference in New Issue
Block a user