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