TransactionsFilterDialog.tsx raw
1 import React from "react";
2 import { Button } from "src/components/ui/button";
3 import { Checkbox } from "src/components/ui/checkbox";
4 import {
5 Dialog,
6 DialogContent,
7 DialogDescription,
8 DialogFooter,
9 DialogHeader,
10 DialogTitle,
11 } from "src/components/ui/dialog";
12 import { Input } from "src/components/ui/input";
13 import { Label } from "src/components/ui/label";
14 import { ToggleGroup, ToggleGroupItem } from "src/components/ui/toggle-group";
15 import {
16 defaultTransactionFilters,
17 type TransactionFilters,
18 } from "src/hooks/useTransactions";
19
20 const TYPE_OPTIONS: { label: string; value: string }[] = [
21 { label: "All", value: "all" },
22 { label: "Sent", value: "outgoing" },
23 { label: "Received", value: "incoming" },
24 ];
25
26 type TransactionsFilterDialogProps = {
27 open: boolean;
28 onOpenChange: (open: boolean) => void;
29 filters: TransactionFilters;
30 onFiltersChange: (filters: TransactionFilters) => void;
31 };
32
33 export function TransactionsFilterDialog({
34 open,
35 onOpenChange,
36 filters,
37 onFiltersChange,
38 }: TransactionsFilterDialogProps) {
39 const [searchTerm, setSearchTerm] = React.useState("");
40 const [type, setType] = React.useState("all");
41 const [minAmountSat, setMinAmountSat] = React.useState("");
42 const [hideFailed, setHideFailed] = React.useState(false);
43
44 React.useEffect(() => {
45 if (open) {
46 setSearchTerm(filters.searchTerm ?? "");
47 setType(filters.type ?? "all");
48 setMinAmountSat(filters.minAmountSat ? String(filters.minAmountSat) : "");
49 setHideFailed(!!filters.hideFailed);
50 }
51 }, [open, filters]);
52
53 function onSubmit(e: React.FormEvent) {
54 e.preventDefault();
55 const parsedMinAmountSat = Number(minAmountSat);
56 onFiltersChange({
57 searchTerm: searchTerm.trim() || undefined,
58 type: type === "incoming" || type === "outgoing" ? type : undefined,
59 minAmountSat:
60 Number.isSafeInteger(parsedMinAmountSat) && parsedMinAmountSat > 0
61 ? parsedMinAmountSat
62 : undefined,
63 hideFailed,
64 });
65 onOpenChange(false);
66 }
67
68 function onReset() {
69 onFiltersChange({ ...defaultTransactionFilters });
70 onOpenChange(false);
71 }
72
73 return (
74 <Dialog open={open} onOpenChange={onOpenChange}>
75 <DialogContent>
76 <form onSubmit={onSubmit}>
77 <DialogHeader>
78 <DialogTitle>Filter Transactions</DialogTitle>
79 <DialogDescription>
80 Choose which payments appear in your transaction list.
81 </DialogDescription>
82 </DialogHeader>
83 <div className="grid gap-2 mt-5">
84 <Label htmlFor="searchTerm">Search</Label>
85 <Input
86 autoFocus
87 id="searchTerm"
88 type="text"
89 placeholder="Description, payment hash, invoice or label"
90 value={searchTerm}
91 onChange={(e) => setSearchTerm(e.target.value)}
92 />
93 </div>
94 <div className="grid gap-2 mt-4">
95 <Label>Direction</Label>
96 <ToggleGroup
97 type="single"
98 variant="outline"
99 value={type}
100 onValueChange={(value) => value && setType(value)}
101 >
102 {TYPE_OPTIONS.map((option) => (
103 <ToggleGroupItem key={option.value} value={option.value}>
104 {option.label}
105 </ToggleGroupItem>
106 ))}
107 </ToggleGroup>
108 </div>
109 <div className="grid gap-2 mt-4">
110 <Label htmlFor="minAmountSat">Minimum amount (sats)</Label>
111 <Input
112 id="minAmountSat"
113 type="number"
114 min="1"
115 placeholder="Show all amounts"
116 value={minAmountSat}
117 onChange={(e) => setMinAmountSat(e.target.value.trim())}
118 />
119 </div>
120 <div className="flex items-center mt-4">
121 <Checkbox
122 id="hideFailed"
123 checked={hideFailed}
124 onCheckedChange={(checked) => setHideFailed(checked === true)}
125 />
126 <Label htmlFor="hideFailed" className="ml-2 cursor-pointer">
127 Hide failed payments
128 </Label>
129 </div>
130 <DialogFooter className="mt-5">
131 <Button type="button" variant="secondary" onClick={onReset}>
132 Reset
133 </Button>
134 <Button type="submit">Apply Filters</Button>
135 </DialogFooter>
136 </form>
137 </DialogContent>
138 </Dialog>
139 );
140 }
141