mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-14 12:02:19 +00:00
refactor(frontend): Simplify filter bar UI and remove advanced filters popover.
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
822
frontend/package-lock.json
generated
822
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -40,8 +40,6 @@ export default function TransactionsTable() {
|
|||||||
selectedAccount: "",
|
selectedAccount: "",
|
||||||
startDate: "",
|
startDate: "",
|
||||||
endDate: "",
|
endDate: "",
|
||||||
minAmount: "",
|
|
||||||
maxAmount: "",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [showRawModal, setShowRawModal] = useState(false);
|
const [showRawModal, setShowRawModal] = useState(false);
|
||||||
@@ -73,8 +71,6 @@ export default function TransactionsTable() {
|
|||||||
selectedAccount: "",
|
selectedAccount: "",
|
||||||
startDate: "",
|
startDate: "",
|
||||||
endDate: "",
|
endDate: "",
|
||||||
minAmount: "",
|
|
||||||
maxAmount: "",
|
|
||||||
});
|
});
|
||||||
setColumnFilters([]);
|
setColumnFilters([]);
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
@@ -116,8 +112,6 @@ export default function TransactionsTable() {
|
|||||||
currentPage,
|
currentPage,
|
||||||
perPage,
|
perPage,
|
||||||
debouncedSearchTerm,
|
debouncedSearchTerm,
|
||||||
filterState.minAmount,
|
|
||||||
filterState.maxAmount,
|
|
||||||
],
|
],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
apiClient.getTransactions({
|
apiClient.getTransactions({
|
||||||
@@ -128,12 +122,6 @@ export default function TransactionsTable() {
|
|||||||
perPage: perPage,
|
perPage: perPage,
|
||||||
search: debouncedSearchTerm || undefined,
|
search: debouncedSearchTerm || undefined,
|
||||||
summaryOnly: false,
|
summaryOnly: false,
|
||||||
minAmount: filterState.minAmount
|
|
||||||
? parseFloat(filterState.minAmount)
|
|
||||||
: undefined,
|
|
||||||
maxAmount: filterState.maxAmount
|
|
||||||
? parseFloat(filterState.maxAmount)
|
|
||||||
: undefined,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -157,8 +145,6 @@ export default function TransactionsTable() {
|
|||||||
filterState.selectedAccount,
|
filterState.selectedAccount,
|
||||||
filterState.startDate,
|
filterState.startDate,
|
||||||
filterState.endDate,
|
filterState.endDate,
|
||||||
filterState.minAmount,
|
|
||||||
filterState.maxAmount,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleViewRaw = (transaction: Transaction) => {
|
const handleViewRaw = (transaction: Transaction) => {
|
||||||
@@ -175,9 +161,7 @@ export default function TransactionsTable() {
|
|||||||
filterState.searchTerm ||
|
filterState.searchTerm ||
|
||||||
filterState.selectedAccount ||
|
filterState.selectedAccount ||
|
||||||
filterState.startDate ||
|
filterState.startDate ||
|
||||||
filterState.endDate ||
|
filterState.endDate;
|
||||||
filterState.minAmount ||
|
|
||||||
filterState.maxAmount;
|
|
||||||
|
|
||||||
|
|
||||||
// Define columns
|
// Define columns
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function AccountCombobox({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
className="justify-between"
|
className="w-full justify-between"
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Building2 className="mr-2 h-4 w-4" />
|
<Building2 className="mr-2 h-4 w-4" />
|
||||||
|
|||||||
@@ -70,30 +70,6 @@ export function ActiveFilterChips({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Amount range chips
|
|
||||||
if (filterState.minAmount || filterState.maxAmount) {
|
|
||||||
let amountLabel = "Amount: ";
|
|
||||||
const minAmount = filterState.minAmount
|
|
||||||
? parseFloat(filterState.minAmount)
|
|
||||||
: null;
|
|
||||||
const maxAmount = filterState.maxAmount
|
|
||||||
? parseFloat(filterState.maxAmount)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (minAmount && maxAmount) {
|
|
||||||
amountLabel += `€${minAmount} - €${maxAmount}`;
|
|
||||||
} else if (minAmount) {
|
|
||||||
amountLabel += `≥ €${minAmount}`;
|
|
||||||
} else if (maxAmount) {
|
|
||||||
amountLabel += `≤ €${maxAmount}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
chips.push({
|
|
||||||
key: "minAmount", // We'll clear both min and max when removing this chip
|
|
||||||
label: amountLabel,
|
|
||||||
value: `${filterState.minAmount}-${filterState.maxAmount}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRemoveChip = (key: keyof FilterState) => {
|
const handleRemoveChip = (key: keyof FilterState) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@@ -102,11 +78,6 @@ export function ActiveFilterChips({
|
|||||||
onFilterChange("startDate", "");
|
onFilterChange("startDate", "");
|
||||||
onFilterChange("endDate", "");
|
onFilterChange("endDate", "");
|
||||||
break;
|
break;
|
||||||
case "minAmount":
|
|
||||||
// Clear both min and max amount
|
|
||||||
onFilterChange("minAmount", "");
|
|
||||||
onFilterChange("maxAmount", "");
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
onFilterChange(key, "");
|
onFilterChange(key, "");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { MoreHorizontal, Euro } from "lucide-react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
|
|
||||||
export interface AdvancedFiltersPopoverProps {
|
|
||||||
minAmount: string;
|
|
||||||
maxAmount: string;
|
|
||||||
onMinAmountChange: (value: string) => void;
|
|
||||||
onMaxAmountChange: (value: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AdvancedFiltersPopover({
|
|
||||||
minAmount,
|
|
||||||
maxAmount,
|
|
||||||
onMinAmountChange,
|
|
||||||
onMaxAmountChange,
|
|
||||||
}: AdvancedFiltersPopoverProps) {
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
const hasAdvancedFilters = minAmount || maxAmount;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant={hasAdvancedFilters ? "default" : "outline"}
|
|
||||||
size="default"
|
|
||||||
className="relative"
|
|
||||||
>
|
|
||||||
<MoreHorizontal className="h-4 w-4 mr-2" />
|
|
||||||
More
|
|
||||||
{hasAdvancedFilters && (
|
|
||||||
<div className="absolute -top-1 -right-1 h-2 w-2 bg-blue-600 rounded-full" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-80" align="end">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h4 className="font-medium leading-none">Advanced Filters</h4>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Additional filters for more precise results
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
|
||||||
Amount Range
|
|
||||||
</label>
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-xs text-muted-foreground">
|
|
||||||
Minimum
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
|
||||||
<Euro className="absolute left-2 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
placeholder="0.00"
|
|
||||||
value={minAmount}
|
|
||||||
onChange={(e) => onMinAmountChange(e.target.value)}
|
|
||||||
className="pl-8"
|
|
||||||
step="0.01"
|
|
||||||
min="0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-xs text-muted-foreground">
|
|
||||||
Maximum
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
|
||||||
<Euro className="absolute left-2 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
placeholder="1000.00"
|
|
||||||
value={maxAmount}
|
|
||||||
onChange={(e) => onMaxAmountChange(e.target.value)}
|
|
||||||
className="pl-8"
|
|
||||||
step="0.01"
|
|
||||||
min="0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
Leave empty for no limit
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Future: Add transaction status filter */}
|
|
||||||
<div className="pt-2 border-t">
|
|
||||||
<div className="text-xs text-muted-foreground">
|
|
||||||
More filters coming soon: transaction status, categories, and
|
|
||||||
more.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Clear advanced filters */}
|
|
||||||
{hasAdvancedFilters && (
|
|
||||||
<div className="pt-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => {
|
|
||||||
onMinAmountChange("");
|
|
||||||
onMaxAmountChange("");
|
|
||||||
}}
|
|
||||||
className="w-full"
|
|
||||||
>
|
|
||||||
Clear Advanced Filters
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import { cn } from "@/lib/utils";
|
|||||||
import { DateRangePicker } from "./DateRangePicker";
|
import { DateRangePicker } from "./DateRangePicker";
|
||||||
import { AccountCombobox } from "./AccountCombobox";
|
import { AccountCombobox } from "./AccountCombobox";
|
||||||
import { ActiveFilterChips } from "./ActiveFilterChips";
|
import { ActiveFilterChips } from "./ActiveFilterChips";
|
||||||
import { AdvancedFiltersPopover } from "./AdvancedFiltersPopover";
|
|
||||||
import type { Account } from "../../types/api";
|
import type { Account } from "../../types/api";
|
||||||
|
|
||||||
export interface FilterState {
|
export interface FilterState {
|
||||||
@@ -12,8 +11,6 @@ export interface FilterState {
|
|||||||
selectedAccount: string;
|
selectedAccount: string;
|
||||||
startDate: string;
|
startDate: string;
|
||||||
endDate: string;
|
endDate: string;
|
||||||
minAmount: string;
|
|
||||||
maxAmount: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FilterBarProps {
|
export interface FilterBarProps {
|
||||||
@@ -37,9 +34,7 @@ export function FilterBar({
|
|||||||
filterState.searchTerm ||
|
filterState.searchTerm ||
|
||||||
filterState.selectedAccount ||
|
filterState.selectedAccount ||
|
||||||
filterState.startDate ||
|
filterState.startDate ||
|
||||||
filterState.endDate ||
|
filterState.endDate;
|
||||||
filterState.minAmount ||
|
|
||||||
filterState.maxAmount;
|
|
||||||
|
|
||||||
const handleDateRangeChange = (startDate: string, endDate: string) => {
|
const handleDateRangeChange = (startDate: string, endDate: string) => {
|
||||||
onFilterChange("startDate", startDate);
|
onFilterChange("startDate", startDate);
|
||||||
@@ -57,48 +52,85 @@ export function FilterBar({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Primary Filters Row */}
|
{/* Primary Filters Row */}
|
||||||
<div className="flex flex-wrap items-center gap-3 mb-4">
|
<div className="space-y-4 mb-4">
|
||||||
{/* Search Input */}
|
{/* Desktop Layout */}
|
||||||
<div className="relative flex-1 min-w-[240px]">
|
<div className="hidden lg:flex items-center justify-between gap-6">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
{/* Left Side: Main Filters */}
|
||||||
<Input
|
<div className="flex items-center gap-3 flex-1">
|
||||||
placeholder="Search transactions..."
|
{/* Search Input */}
|
||||||
value={filterState.searchTerm}
|
<div className="relative w-[200px]">
|
||||||
onChange={(e) => onFilterChange("searchTerm", e.target.value)}
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
className="pl-9 pr-8 bg-background"
|
<Input
|
||||||
/>
|
placeholder="Search transactions..."
|
||||||
{isSearchLoading && (
|
value={filterState.searchTerm}
|
||||||
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
|
onChange={(e) => onFilterChange("searchTerm", e.target.value)}
|
||||||
<div className="animate-spin h-4 w-4 border-2 border-border border-t-primary rounded-full"></div>
|
className="pl-9 pr-8 bg-background"
|
||||||
|
/>
|
||||||
|
{isSearchLoading && (
|
||||||
|
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
|
||||||
|
<div className="animate-spin h-4 w-4 border-2 border-border border-t-primary rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
{/* Account Selection */}
|
||||||
|
<AccountCombobox
|
||||||
|
accounts={accounts}
|
||||||
|
selectedAccount={filterState.selectedAccount}
|
||||||
|
onAccountChange={(accountId) =>
|
||||||
|
onFilterChange("selectedAccount", accountId)
|
||||||
|
}
|
||||||
|
className="w-[180px]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Date Range Picker */}
|
||||||
|
<DateRangePicker
|
||||||
|
startDate={filterState.startDate}
|
||||||
|
endDate={filterState.endDate}
|
||||||
|
onDateRangeChange={handleDateRangeChange}
|
||||||
|
className="w-[220px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Account Selection */}
|
{/* Mobile Layout */}
|
||||||
<AccountCombobox
|
<div className="lg:hidden space-y-3">
|
||||||
accounts={accounts}
|
{/* First Row: Search Input (Full Width) */}
|
||||||
selectedAccount={filterState.selectedAccount}
|
<div className="relative">
|
||||||
onAccountChange={(accountId) =>
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
onFilterChange("selectedAccount", accountId)
|
<Input
|
||||||
}
|
placeholder="Search..."
|
||||||
className="w-[200px]"
|
value={filterState.searchTerm}
|
||||||
/>
|
onChange={(e) => onFilterChange("searchTerm", e.target.value)}
|
||||||
|
className="pl-9 pr-8 bg-background w-full"
|
||||||
|
/>
|
||||||
|
{isSearchLoading && (
|
||||||
|
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
|
||||||
|
<div className="animate-spin h-4 w-4 border-2 border-border border-t-primary rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Date Range Picker */}
|
{/* Second Row: Account Selection (Full Width) */}
|
||||||
<DateRangePicker
|
<AccountCombobox
|
||||||
startDate={filterState.startDate}
|
accounts={accounts}
|
||||||
endDate={filterState.endDate}
|
selectedAccount={filterState.selectedAccount}
|
||||||
onDateRangeChange={handleDateRangeChange}
|
onAccountChange={(accountId) =>
|
||||||
className="w-[240px]"
|
onFilterChange("selectedAccount", accountId)
|
||||||
/>
|
}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Advanced Filters Button */}
|
{/* Third Row: Date Range */}
|
||||||
<AdvancedFiltersPopover
|
<DateRangePicker
|
||||||
minAmount={filterState.minAmount}
|
startDate={filterState.startDate}
|
||||||
maxAmount={filterState.maxAmount}
|
endDate={filterState.endDate}
|
||||||
onMinAmountChange={(value) => onFilterChange("minAmount", value)}
|
onDateRangeChange={handleDateRangeChange}
|
||||||
onMaxAmountChange={(value) => onFilterChange("maxAmount", value)}
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Active Filter Chips */}
|
{/* Active Filter Chips */}
|
||||||
|
|||||||
@@ -2,5 +2,4 @@ export { FilterBar } from "./FilterBar";
|
|||||||
export { DateRangePicker } from "./DateRangePicker";
|
export { DateRangePicker } from "./DateRangePicker";
|
||||||
export { AccountCombobox } from "./AccountCombobox";
|
export { AccountCombobox } from "./AccountCombobox";
|
||||||
export { ActiveFilterChips } from "./ActiveFilterChips";
|
export { ActiveFilterChips } from "./ActiveFilterChips";
|
||||||
export { AdvancedFiltersPopover } from "./AdvancedFiltersPopover";
|
|
||||||
export type { FilterState, FilterBarProps } from "./FilterBar";
|
export type { FilterState, FilterBarProps } from "./FilterBar";
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ function RootLayout() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1 min-w-0">
|
||||||
<Header setSidebarOpen={setSidebarOpen} />
|
<Header setSidebarOpen={setSidebarOpen} />
|
||||||
<main className="flex-1 p-6">
|
<main className="flex-1 p-6 min-w-0">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user