feat(ui): ek ekleme alanı, carousel görüntüleyici, ekstre sayfası ve mutabakat penceresi

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-05 07:55:04 +03:00
parent 2e2e33233f
commit 8a9f5ddc7d
12 changed files with 1566 additions and 314 deletions
@@ -27,6 +27,9 @@ import { cn } from "@/lib/utils";
import { todayIstanbul } from "@/shared/dates";
import { formatMoney } from "@/shared/format";
import { formatMoneyInput, parseMoney } from "@/shared/money";
import { allowedAttachmentKinds } from "@/shared/attachments";
import { AttachmentPicker } from "@/features/attachments/components/attachment-picker";
import type { AttachmentInfo } from "@/features/attachments/service";
import { transactionFormSchema, type TransactionForm } from "@/shared/schemas/transaction";
import { MAX_SPLITS, TRANSACTION_TYPES, type TransactionType } from "@/shared/transactions";
import {
@@ -57,6 +60,8 @@ type FormValues = {
payee: string;
description: string;
tags: string;
/** Önizleme bilgisiyle ekler; sunucuya yalnızca kimlikleri gider. */
attachments: AttachmentInfo[];
};
function toValues(mode: TransactionFormMode, options: TransactionFormOptions | null): FormValues {
@@ -72,6 +77,7 @@ function toValues(mode: TransactionFormMode, options: TransactionFormOptions | n
payee: "",
description: "",
tags: "",
attachments: [],
};
}
const x = mode.transaction;
@@ -90,6 +96,8 @@ function toValues(mode: TransactionFormMode, options: TransactionFormOptions | n
payee: x.payeeName,
description: x.description,
tags: x.tags.join(", "),
// Kopyada ekler taşınmaz (fiş o işleme aittir).
attachments: mode.kind === "edit" ? x.attachments : [],
};
}
@@ -298,6 +306,7 @@ export function TransactionFormDialog({
const [loadError, setLoadError] = useState<string | null>(null);
const [advancedOpen, setAdvancedOpen] = useState(false);
const [suggested, setSuggested] = useState(false);
const [uploadBusy, setUploadBusy] = useState(false);
const form = useForm<FormValues, unknown, TransactionForm>({
// FormValues, şemanın giriş şekliyle birebir (tutarlar string; zod kuruşa çevirir).
resolver: zodResolver(transactionFormSchema) as unknown as Resolver<
@@ -332,6 +341,7 @@ export function TransactionFormDialog({
const amount = useWatch({ control: form.control, name: "amount" });
const accountId = useWatch({ control: form.control, name: "accountId" });
const splitValues = useWatch({ control: form.control, name: "splits" });
const attachmentCount = useWatch({ control: form.control, name: "attachments" })?.length ?? 0;
const splitMode = splits.fields.length > 0;
// Düzenlemede arşivli/kapalı hesap veya kategori seçenek listesinde yoksa ekle (kaybolmasın).
@@ -431,9 +441,14 @@ export function TransactionFormDialog({
async function onSubmit() {
if (!mode) return;
const values = form.getValues();
if (uploadBusy) {
form.setError("root", { message: tr.attachments.waitForUploads });
return;
}
const { attachments, ...values } = form.getValues();
const payload = {
...values,
attachmentIds: attachments.map((a) => a.id),
toAccountId: values.type === "transfer" ? values.toAccountId : "",
categoryId: values.type === "transfer" || values.splits.length ? "" : values.categoryId,
splits: values.type === "transfer" ? [] : values.splits,
@@ -479,7 +494,7 @@ export function TransactionFormDialog({
<Button
type="submit"
form="transaction-form"
disabled={form.formState.isSubmitting || loading}
disabled={form.formState.isSubmitting || loading || uploadBusy}
>
{form.formState.isSubmitting ? <Loader2 className="animate-spin" /> : null}
{tr.common.save}
@@ -674,10 +689,30 @@ export function TransactionFormDialog({
· {t.list.split(splits.fields.length)}
</span>
) : null}
{attachmentCount ? (
<span className="text-xs text-muted-foreground">
· {tr.attachments.count(attachmentCount)}
</span>
) : null}
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="pt-3">
<FieldGroup>
<Controller
name="attachments"
control={form.control}
render={({ field }) => (
<Field>
<FieldLabel>{tr.attachments.title}</FieldLabel>
<AttachmentPicker
value={field.value}
onChange={field.onChange}
allowedKinds={allowedAttachmentKinds(type)}
onBusyChange={setUploadBusy}
/>
</Field>
)}
/>
<Controller
name="tags"
control={form.control}