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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user