Newer
Older
CrypticOreWallet / src / pages / transactions / TransactionsTable.tsx
// Copyright (c) 2020-2021 Drew Lemmy
// This file is part of KristWeb 2 under GPL-3.0.
// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
import React, { useState, useEffect, Dispatch, SetStateAction } from "react";
import { Table } from "antd";

import { useTranslation } from "react-i18next";

import { KristTransaction } from "../../krist/api/types";
import { convertSorterOrder, lookupTransactions, LookupTransactionsOptions, LookupTransactionsResponse, LookupTransactionType, SortableTransactionFields } from "../../krist/api/lookup";

import { ListingType } from "./TransactionsPage";

import { TransactionType, TYPES_SHOW_VALUE } from "../../components/transactions/TransactionType";
import { ContextualAddress } from "../../components/ContextualAddress";
import { KristValue } from "../../components/KristValue";
import { KristNameLink } from "../../components/KristNameLink";
import { TransactionConciseMetadata } from "../../components/transactions/TransactionConciseMetadata";
import { DateTime } from "../../components/DateTime";

import Debug from "debug";
const debug = Debug("kristweb:transactions-table");

// Received 'Cannot access LookupTransactionType before initialization' here,
// this is a crude workaround
const LISTING_TYPE_MAP: Record<ListingType, LookupTransactionType> = {
  [0]: LookupTransactionType.TRANSACTIONS,
  [1]: LookupTransactionType.TRANSACTIONS,
  [2]: LookupTransactionType.TRANSACTIONS,
  [3]: LookupTransactionType.NAME_HISTORY,
  [4]: LookupTransactionType.NAME_TRANSACTIONS
};

interface Props {
  listingType: ListingType;

  // Number used to trigger a refresh of the transactions listing
  refreshingID?: number;

  addresses?: string[];
  name?: string;

  includeMined?: boolean;
  setError?: Dispatch<SetStateAction<Error | undefined>>;
}

export function TransactionsTable({ listingType, refreshingID, addresses, name, includeMined, setError }: Props): JSX.Element {
  const { t } = useTranslation();

  const [loading, setLoading] = useState(true);
  const [res, setRes] = useState<LookupTransactionsResponse>();
  const [options, setOptions] = useState<LookupTransactionsOptions>({
    limit: 20,
    offset: 0,
    orderBy: "time", // Equivalent to sorting by ID
    order: "DESC"
  });

  // Fetch the transactions from the API, mapping the table options
  useEffect(() => {
    debug("looking up transactions for %s", name || (addresses ? addresses.join(",") : "network"));
    setLoading(true);

    lookupTransactions(name ? [name] : addresses, {
      ...options,
      includeMined,
      type: LISTING_TYPE_MAP[listingType]
    })
      .then(setRes)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [listingType, refreshingID, addresses, name, setError, options, includeMined]);

  debug("results? %b  res.transactions.length: %d  res.count: %d  res.total: %d", !!res, res?.transactions?.length, res?.count, res?.total);

  return <Table<KristTransaction>
    className="transactions-table"
    size="small"

    loading={loading}
    dataSource={res?.transactions || []}
    rowKey="id"

    // Triggered whenever the filter, sorting, or pagination changes
    onChange={(pagination, _, sorter) => {
      // While the pagination should never be undefined, it's important to
      // ensure that the default pageSize here is equal to the pagination's
      // default pageSize, otherwise ant-design will print a warning when the
      // data is first populated.
      const pageSize = (pagination?.pageSize) || 20;

      // This will trigger a data re-fetch
      setOptions({
        ...options,

        limit: pageSize,
        offset: pageSize * ((pagination?.current || 1) - 1),

        orderBy: sorter instanceof Array ? undefined : sorter.field as SortableTransactionFields,
        order: sorter instanceof Array ? undefined : convertSorterOrder(sorter.order),
      });
    }}

    pagination={{
      size: "default",
      position: ["topRight", "bottomRight"],

      showSizeChanger: true,
      defaultPageSize: 20,

      total: res?.total || 0,
      showTotal: total => t("transactions.tableTotal", { count: total || 0 })
    }}

    columns={[
      // ID
      {
        title: t("transactions.columnID"),
        dataIndex: "id", key: "id",

        render: id => id.toLocaleString(),
        width: 100

        // Don't allow sorting by ID to save a bit of width in the columns;
        // it's equivalent to sorting by time anyway
      },
      // Type
      {
        title: t("transactions.columnType"),
        dataIndex: "type", key: "type",
        render: (_, tx) => <TransactionType transaction={tx} />
      },

      // From
      {
        title: t("transactions.columnFrom"),
        dataIndex: "from", key: "from",

        render: (from, tx) => from && (
          <ContextualAddress
            className="transactions-table-address"
            address={from}
            metadata={tx.metadata}
            allowWrap
          />
        ),

        sorter: true
      },
      // To
      {
        title: t("transactions.columnTo"),
        dataIndex: "to", key: "to",

        render: (to, tx) => to && tx.type !== "name_a_record" && (
          <ContextualAddress
            className="transactions-table-address"
            address={to}
            metadata={tx.metadata}
            allowWrap
          />
        ),

        sorter: true
      },

      // Value
      {
        title: t("transactions.columnValue"),
        dataIndex: "value", key: "value",

        render: (value, tx) => TYPES_SHOW_VALUE.includes(tx.type) && (
          <KristValue value={value} />
        ),
        width: 100,

        sorter: true
      },

      // Name
      {
        title: t("transactions.columnName"),
        dataIndex: "name", key: "name",

        render: name => <KristNameLink name={name} />,

        sorter: true
      },

      // Metadata
      {
        title: t("transactions.columnMetadata"),
        dataIndex: "metadata", key: "metadata",

        render: (_, transaction) => <TransactionConciseMetadata transaction={transaction} />,
        width: 260
      },

      // Time
      {
        title: t("transactions.columnTime"),
        dataIndex: "time", key: "time",
        render: time => <DateTime date={time} />,
        width: 200,

        sorter: true,
        defaultSortOrder: "descend"
      }
    ]}
  />;
}