From 9ff410e9f9726cb6910bd06f0e4509b3169823e0 Mon Sep 17 00:00:00 2001 From: Le-King-Fu Date: Tue, 10 Feb 2026 12:02:23 +0000 Subject: [PATCH] fix: searchable category combobox, import source upsert, and intra-batch duplicate detection Replace native { + setQuery(e.target.value); + setHighlightIndex(0); + if (!open) setOpen(true); + }} + onFocus={() => { + setOpen(true); + setQuery(""); + setHighlightIndex(0); + }} + onKeyDown={handleKeyDown} + className={`w-full ${px} ${py} text-sm rounded-lg border border-[var(--border)] bg-[var(--background)] text-[var(--foreground)] placeholder:text-[var(--muted-foreground)] focus:outline-none focus:ring-1 focus:ring-[var(--primary)]`} + /> + {open && totalItems > 0 && ( + + )} + + ); +} diff --git a/src/components/transactions/TransactionFilterBar.tsx b/src/components/transactions/TransactionFilterBar.tsx index 817346c..1fcad90 100644 --- a/src/components/transactions/TransactionFilterBar.tsx +++ b/src/components/transactions/TransactionFilterBar.tsx @@ -1,6 +1,8 @@ +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { Search } from "lucide-react"; import type { TransactionFilters, Category, ImportSource } from "../../shared/types"; +import CategoryCombobox from "../shared/CategoryCombobox"; interface TransactionFilterBarProps { filters: TransactionFilters; @@ -17,6 +19,14 @@ export default function TransactionFilterBar({ }: TransactionFilterBarProps) { const { t } = useTranslation(); + const categoryExtras = useMemo( + () => [ + { value: "", label: t("transactions.filters.allCategories") }, + { value: "uncategorized", label: t("transactions.filters.uncategorized") }, + ], + [t] + ); + const activeCount = [ filters.search, filters.categoryId !== null || filters.uncategorizedOnly, @@ -44,34 +54,34 @@ export default function TransactionFilterBar({ {/* Category */} - + onExtraSelect={(val) => { + if (val === "uncategorized") { + onFilterChange("uncategorizedOnly", true); + onFilterChange("categoryId", null); + } else { + onFilterChange("uncategorizedOnly", false); + onFilterChange("categoryId", null); + } + }} + /> + {/* Source */} - onCategoryChange( - row.id, - e.target.value ? Number(e.target.value) : null - ) - } - className="w-full px-2 py-1 text-sm rounded border border-[var(--border)] bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:ring-1 focus:ring-[var(--primary)]" - > - - {categories.map((c) => ( - - ))} - + onCategoryChange(row.id, id)} + placeholder={t("transactions.table.noCategory")} + compact + extraOptions={noCategoryExtra} + activeExtra={row.category_id === null ? "" : null} + onExtraSelect={() => onCategoryChange(row.id, null)} + />