diff --git a/src/krist/wallets/utils.ts b/src/krist/wallets/utils.ts index 1c9c56e..5a5626b 100644 --- a/src/krist/wallets/utils.ts +++ b/src/krist/wallets/utils.ts @@ -1,12 +1,16 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under AGPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { useMemo } from "react"; + import { useSelector, shallowEqual } from "react-redux"; import { RootState } from "@store"; import { Wallet, WalletNew, WalletMap, WalletFormatName, applyWalletFormat } from "."; import { makeV2Address } from "../addressAlgo"; +import { localeSort } from "@utils"; + /** Finds a wallet in the wallet map by the given Krist address. */ export function findWalletByAddress( wallets: WalletMap, @@ -60,6 +64,27 @@ return { wallets, walletAddressMap, addressList, joinedAddressList }; } +export interface WalletCategoriesHookResponse { + categories: string[]; + joinedCategoryList: string; +} + +export function useWalletCategories(): WalletCategoriesHookResponse { + const wallets = useSelector((s: RootState) => s.wallets.wallets, shallowEqual); + + const categories = useMemo(() => { + const cats = [...new Set(Object.values(wallets) + .filter(w => w.category !== undefined && w.category !== "") + .map(w => w.category) as string[])]; + localeSort(cats); + return cats; + }, [wallets]); + + const joinedCategoryList = categories.join(","); + + return { categories, joinedCategoryList }; +} + /** Almost anywhere you'd want to apply a wallet format, you'd also want to * calculate the v2 address, so just do both at once! */ export async function calculateAddress( diff --git a/src/pages/blocks/BlocksTable.tsx b/src/pages/blocks/BlocksTable.tsx index 545d12e..0450e0b 100644 --- a/src/pages/blocks/BlocksTable.tsx +++ b/src/pages/blocks/BlocksTable.tsx @@ -1,10 +1,11 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under AGPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { useState, useEffect, Dispatch, SetStateAction } from "react"; +import { useState, useEffect, useMemo, Dispatch, SetStateAction } from "react"; import { Table, TablePaginationConfig } from "antd"; +import { ColumnsType } from "antd/lib/table"; -import { useTranslation } from "react-i18next"; +import { useTFns, TStrFn } from "@utils/i18n"; import { Link } from "react-router-dom"; import { KristBlock } from "@api/types"; @@ -30,8 +31,88 @@ setPagination?: Dispatch>; } +function getColumns( + tStr: TStrFn, + dateColumnWidth: number, + lowest: boolean | undefined +): ColumnsType { + return [ + // Height + { + title: tStr("columnHeight"), + dataIndex: "height", key: "height", + + render: height => ( + + {height.toLocaleString()} + + ), + width: 100 + }, + + // Miner address + { + title: tStr("columnAddress"), + dataIndex: "address", key: "address", + + render: address => address && ( + + ), + + sorter: true + }, + + // Hash + { + title: tStr("columnHash"), + dataIndex: "hash", key: "hash", + + render: hash => , + + sorter: true, + defaultSortOrder: lowest ? "ascend" : undefined + }, + + // Value + { + title: tStr("columnValue"), + dataIndex: "value", key: "value", + + render: value => , + width: 100, + + sorter: true + }, + + // Difficulty + { + title: tStr("columnDifficulty"), + dataIndex: "difficulty", key: "difficulty", + + render: difficulty => difficulty.toLocaleString(), + + sorter: true + }, + + // Time + { + title: tStr("columnTime"), + dataIndex: "time", key: "time", + render: time => , + width: dateColumnWidth, + + sorter: true, + defaultSortOrder: lowest ? undefined : "descend" + } + ]; +} + export function BlocksTable({ refreshingID, lowest, setError, setPagination }: Props): JSX.Element { - const { t } = useTranslation(); + const { tStr, tKey } = useTFns("blocks."); const [loading, setLoading] = useState(true); const [res, setRes] = useState(); @@ -42,12 +123,16 @@ const { paginationTableProps, hotkeys } = useMalleablePagination( res, res?.blocks, - "blocks.tableTotal", + tKey("tableTotal"), options, setOptions, setPagination ); const dateColumnWidth = useDateColumnWidth(); + const columns = useMemo(() => getColumns( + tStr, dateColumnWidth, lowest + ), [tStr, dateColumnWidth, lowest]); + // Fetch the blocks from the API, mapping the table options useEffect(() => { debug("looking up blocks"); @@ -71,79 +156,7 @@ {...paginationTableProps} - columns={[ - // Height - { - title: t("blocks.columnHeight"), - dataIndex: "height", key: "height", - - render: height => ( - - {height.toLocaleString()} - - ), - width: 100 - }, - - // Miner address - { - title: t("blocks.columnAddress"), - dataIndex: "address", key: "address", - - render: address => address && ( - - ), - - sorter: true - }, - - // Hash - { - title: t("blocks.columnHash"), - dataIndex: "hash", key: "hash", - - render: hash => , - - sorter: true, - defaultSortOrder: lowest ? "ascend" : undefined - }, - - // Value - { - title: t("blocks.columnValue"), - dataIndex: "value", key: "value", - - render: value => , - width: 100, - - sorter: true - }, - - // Difficulty - { - title: t("blocks.columnDifficulty"), - dataIndex: "difficulty", key: "difficulty", - - render: difficulty => difficulty.toLocaleString(), - - sorter: true - }, - - // Time - { - title: t("blocks.columnTime"), - dataIndex: "time", key: "time", - render: time => , - width: dateColumnWidth, - - sorter: true, - defaultSortOrder: lowest ? undefined : "descend" - } - ]} + columns={columns} />; return <> diff --git a/src/pages/contacts/ContactsTable.tsx b/src/pages/contacts/ContactsTable.tsx index 3c8bbc8..f5bac32 100644 --- a/src/pages/contacts/ContactsTable.tsx +++ b/src/pages/contacts/ContactsTable.tsx @@ -1,13 +1,15 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under AGPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { useMemo } from "react"; import { Table } from "antd"; +import { ColumnsType } from "antd/lib/table"; -import { useTFns } from "@utils/i18n"; +import { useTFns, TStrFn } from "@utils/i18n"; import { ContextualAddress } from "@comp/addresses/ContextualAddress"; -import { useContacts } from "@contacts"; +import { useContacts, Contact } from "@contacts"; import { ContactActions } from "./ContactActions"; import { OpenEditContactFn } from "./ContactEditButton"; import { OpenSendTxFn } from "@comp/transactions/SendTransactionModalLink"; @@ -19,6 +21,50 @@ openSendTx: OpenSendTxFn; } +function getColumns( + tStr: TStrFn, + openEditContact: OpenEditContactFn, + openSendTx: OpenSendTxFn +): ColumnsType { + return [ + // Label + { + title: tStr("columnLabel"), + dataIndex: "label", key: "label", + sorter: keyedNullSort("label", true) + }, + + // Address + { + title: tStr("columnAddress"), + dataIndex: "address", key: "address", + + render: address => ( + + ), + sorter: (a, b) => a.address.localeCompare(b.address) + }, + + // Actions + { + key: "actions", + width: 80, + + render: (_, contact) => ( + + ) + } + ]; +} + export function ContactsTable({ openEditContact, openSendTx @@ -26,6 +72,10 @@ const { tStr } = useTFns("addressBook."); const { contacts } = useContacts(); + const columns = useMemo(() => getColumns( + tStr, openEditContact, openSendTx + ), [tStr, openEditContact, openSendTx]); + return ( - - ), - sorter: (a, b) => a.address.localeCompare(b.address) - }, - - // Actions - { - key: "actions", - width: 80, - - render: (_, contact) => ( - - ) - } - ]} + columns={columns} />; } diff --git a/src/pages/names/NamesTable.tsx b/src/pages/names/NamesTable.tsx index ae2ef6d..f3f5cf0 100644 --- a/src/pages/names/NamesTable.tsx +++ b/src/pages/names/NamesTable.tsx @@ -1,10 +1,11 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under AGPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { useState, useEffect, Dispatch, SetStateAction } from "react"; +import { useState, useEffect, useCallback, useMemo, Dispatch, SetStateAction } from "react"; import { Table, TablePaginationConfig, Tag } from "antd"; +import { ColumnsType } from "antd/lib/table"; -import { useTranslation } from "react-i18next"; +import { useTFns, TStrFn } from "@utils/i18n"; import { KristName } from "@api/types"; import { lookupNames, LookupNamesOptions, LookupNamesResponse } from "@api/lookup"; @@ -12,7 +13,7 @@ useMalleablePagination, useTableHistory, useDateColumnWidth } from "@utils/table"; -import { useWallets } from "@wallets"; +import { useWallets, WalletAddressMap } from "@wallets"; import { NameActions } from "./mgmt/NameActions"; import { OpenEditNameFn } from "./mgmt/NameEditModalLink"; import { OpenSendTxFn } from "@comp/transactions/SendTransactionModalLink"; @@ -41,6 +42,121 @@ openSendTx: OpenSendTxFn; } +function getColumns( + tStr: TStrFn, + dateColumnWidth: number, + sortNew: boolean | undefined, + walletAddressMap: WalletAddressMap, + openNameEdit: OpenEditNameFn, + openSendTx: OpenSendTxFn +): ColumnsType { + return [ + // Name + { + title: tStr("columnName"), + dataIndex: "name", key: "name", + + render: name => , + + sorter: true, + defaultSortOrder: sortNew ? undefined : "ascend" + }, + + // Owner + { + title: tStr("columnOwner"), + dataIndex: "owner", key: "owner", + + render: owner => owner && ( + + ), + + sorter: true + }, + + // Original owner + { + title: tStr("columnOriginalOwner"), + dataIndex: "original_owner", key: "original_owner", + + render: owner => owner && ( + + ), + + sorter: true + }, + + // A record + { + title: tStr("columnARecord"), + dataIndex: "a", key: "a", + + render: a => , + + sorter: true + }, + + // Unpaid blocks + { + title: tStr("columnUnpaid"), + dataIndex: "unpaid", key: "unpaid", + + render: unpaid => unpaid > 0 + ? {unpaid.toLocaleString()} + : <>, + width: 50, + + sorter: true + }, + + // Registered time + { + title: tStr("columnRegistered"), + dataIndex: "registered", key: "registered", + + render: time => , + width: dateColumnWidth, + + sorter: true, + defaultSortOrder: sortNew ? "descend" : undefined + }, + + // Updated time + { + title: tStr("columnUpdated"), + dataIndex: "updated", key: "updated", + + render: time => , + width: dateColumnWidth, + + sorter: true + }, + + // Actions + { + key: "actions", + width: 100, // Force it to be minimum size + render: (_, record) => ( + + ) + } + ]; +} + export function NamesTable({ refreshingID, @@ -53,7 +169,7 @@ openNameEdit, openSendTx }: Props): JSX.Element { - const { t } = useTranslation(); + const { tStr, tKey } = useTFns("names."); const [loading, setLoading] = useState(true); const [res, setRes] = useState(); @@ -64,14 +180,19 @@ const { paginationTableProps, hotkeys } = useMalleablePagination( res, res?.names, - "names.tableTotal", + tKey("tableTotal"), options, setOptions, setPagination ); const dateColumnWidth = useDateColumnWidth(); // Used to change the actions depending on whether or not we own the name - const { walletAddressMap } = useWallets(); + const { walletAddressMap, joinedAddressList } = useWallets(); + + const columns = useMemo(() => getColumns( + tStr, dateColumnWidth, sortNew, walletAddressMap, openNameEdit, openSendTx + // eslint-disable-next-line react-hooks/exhaustive-deps + ), [tStr, dateColumnWidth, sortNew, joinedAddressList, openNameEdit, openSendTx]); // Used to pause the table lookups when performing a bulk name edit const locked = useNameTableLock(); @@ -94,6 +215,9 @@ debug("results? %b res.names.length: %d res.count: %d res.total: %d", !!res, res?.names?.length, res?.count, res?.total); + const getRowClasses = useCallback((name: KristName): string => + name.unpaid > 0 ? "name-row-unpaid" : "", []); + const tbl = className="names-table" size="small" @@ -104,113 +228,9 @@ {...paginationTableProps} - rowClassName={name => name.unpaid > 0 ? "name-row-unpaid" : ""} + rowClassName={getRowClasses} - columns={[ - // Name - { - title: t("names.columnName"), - dataIndex: "name", key: "name", - - render: name => , - - sorter: true, - defaultSortOrder: sortNew ? undefined : "ascend" - }, - - // Owner - { - title: t("names.columnOwner"), - dataIndex: "owner", key: "owner", - - render: owner => owner && ( - - ), - - sorter: true - }, - - // Original owner - { - title: t("names.columnOriginalOwner"), - dataIndex: "original_owner", key: "original_owner", - - render: owner => owner && ( - - ), - - sorter: true - }, - - // A record - { - title: t("names.columnARecord"), - dataIndex: "a", key: "a", - - render: a => , - - sorter: true - }, - - // Unpaid blocks - { - title: t("names.columnUnpaid"), - dataIndex: "unpaid", key: "unpaid", - - render: unpaid => unpaid > 0 - ? {unpaid.toLocaleString()} - : <>, - width: 50, - - sorter: true - }, - - // Registered time - { - title: t("names.columnRegistered"), - dataIndex: "registered", key: "registered", - - render: time => , - width: dateColumnWidth, - - sorter: true, - defaultSortOrder: sortNew ? "descend" : undefined - }, - - // Updated time - { - title: t("names.columnUpdated"), - dataIndex: "updated", key: "updated", - - render: time => , - width: dateColumnWidth, - - sorter: true - }, - - // Actions - { - key: "actions", - width: 100, // Force it to be minimum size - render: (_, record) => ( - - ) - } - ]} + columns={columns} />; return <> diff --git a/src/pages/transactions/TransactionsTable.tsx b/src/pages/transactions/TransactionsTable.tsx index a3e46ca..bb4c19c 100644 --- a/src/pages/transactions/TransactionsTable.tsx +++ b/src/pages/transactions/TransactionsTable.tsx @@ -1,11 +1,12 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under AGPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { useState, useEffect, Dispatch, SetStateAction } from "react"; +import { useState, useEffect, useCallback, useMemo, Dispatch, SetStateAction } from "react"; import classNames from "classnames"; import { Table, TablePaginationConfig } from "antd"; +import { ColumnsType } from "antd/lib/table"; -import { useTranslation } from "react-i18next"; +import { useTFns, TStrFn } from "@utils/i18n"; import { Link } from "react-router-dom"; import { KristTransaction } from "@api/types"; @@ -75,6 +76,112 @@ } } +function getColumns( + tStr: TStrFn, + dateColumnWidth: number +): ColumnsType { + return [ + // ID + { + title: tStr("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: tStr("columnType"), + dataIndex: "type", key: "type", + render: (_, tx) => + }, + + // From + { + title: tStr("columnFrom"), + dataIndex: "from", key: "from", + + render: (from, tx) => from && tx.type !== "mined" && ( + + ), + + sorter: true + }, + // To + { + title: tStr("columnTo"), + dataIndex: "to", key: "to", + + render: (to, tx) => to && tx.type !== "name_purchase" && tx.type !== "name_a_record" && ( + + ), + + sorter: true + }, + + // Value + { + title: tStr("columnValue"), + dataIndex: "value", key: "value", + + render: (value, tx) => TYPES_SHOW_VALUE.includes(tx.type) && ( + + ), + width: 100, + + sorter: true + }, + + // Name + { + title: tStr("columnName"), + dataIndex: "name", key: "name", + + render: name => , + + sorter: true + }, + + // Metadata + { + title: tStr("columnMetadata"), + dataIndex: "metadata", key: "metadata", + + render: (_, transaction) => , + width: 260 + }, + + // Time + { + title: tStr("columnTime"), + dataIndex: "time", key: "time", + render: time => , + width: dateColumnWidth, + + sorter: true, + defaultSortOrder: "descend" + } + ]; +} + export function TransactionsTable({ listingType, refreshingID, @@ -82,7 +189,7 @@ includeMined, setError, setPagination }: Props): JSX.Element { - const { t } = useTranslation(); + const { tStr, tKey } = useTFns("transactions."); const [loading, setLoading] = useState(true); const [res, setRes] = useState(); @@ -93,7 +200,7 @@ const { paginationTableProps, hotkeys } = useMalleablePagination( res, res?.transactions, - "transactions.tableTotal", + tKey("tableTotal"), options, setOptions, setPagination ); @@ -102,8 +209,12 @@ listingType !== ListingType.WALLETS; const highlightVerified = useBooleanSetting("transactionsHighlightVerified"); + const columns = useMemo(() => getColumns( + tStr, dateColumnWidth + ), [tStr, dateColumnWidth]); + // Used to highlight own transactions - const { walletAddressMap } = useWallets(); + const { walletAddressMap, joinedAddressList } = useWallets(); // Fetch the transactions from the API, mapping the table options useEffect(() => { @@ -133,7 +244,7 @@ debug("results? %b res.transactions.length: %d res.count: %d res.total: %d", !!res, res?.transactions?.length, res?.count, res?.total); - function getRowClasses(tx: KristTransaction): string { + const getRowClasses = useCallback((tx: KristTransaction): string => { return classNames({ // Highlight own transactions "transaction-row-own": highlightOwn @@ -144,7 +255,8 @@ "transaction-row-verified": highlightVerified && (!!getVerified(tx.from) || !!getVerified(tx.to)), }); - } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [highlightOwn, highlightVerified, joinedAddressList]); const tbl = className="transactions-table" @@ -159,106 +271,7 @@ // Highlight own transactions and verified addresses rowClassName={getRowClasses} - 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) => - }, - - // From - { - title: t("transactions.columnFrom"), - dataIndex: "from", key: "from", - - render: (from, tx) => from && tx.type !== "mined" && ( - - ), - - sorter: true - }, - // To - { - title: t("transactions.columnTo"), - dataIndex: "to", key: "to", - - render: (to, tx) => to && tx.type !== "name_purchase" && tx.type !== "name_a_record" && ( - - ), - - sorter: true - }, - - // Value - { - title: t("transactions.columnValue"), - dataIndex: "value", key: "value", - - render: (value, tx) => TYPES_SHOW_VALUE.includes(tx.type) && ( - - ), - width: 100, - - sorter: true - }, - - // Name - { - title: t("transactions.columnName"), - dataIndex: "name", key: "name", - - render: name => , - - sorter: true - }, - - // Metadata - { - title: t("transactions.columnMetadata"), - dataIndex: "metadata", key: "metadata", - - render: (_, transaction) => , - width: 260 - }, - - // Time - { - title: t("transactions.columnTime"), - dataIndex: "time", key: "time", - render: time => , - width: dateColumnWidth, - - sorter: true, - defaultSortOrder: "descend" - } - ]} + columns={columns} />; return <> diff --git a/src/pages/wallets/WalletsTable.tsx b/src/pages/wallets/WalletsTable.tsx index f7dabb8..90f45f5 100644 --- a/src/pages/wallets/WalletsTable.tsx +++ b/src/pages/wallets/WalletsTable.tsx @@ -1,21 +1,23 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under AGPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import { useMemo } from "react"; import { Table, Tooltip, Tag } from "antd"; +import { ColumnsType } from "antd/lib/table"; -import { useTranslation } from "react-i18next"; +import { useTFns, TStrFn } from "@utils/i18n"; import { ContextualAddress } from "@comp/addresses/ContextualAddress"; import { KristValue } from "@comp/krist/KristValue"; import { DateTime } from "@comp/DateTime"; -import { useWallets } from "@wallets"; +import { useWallets, useWalletCategories, Wallet } from "@wallets"; import { WalletActions } from "./WalletActions"; import { OpenEditWalletFn } from "./WalletEditButton"; import { OpenSendTxFn } from "@comp/transactions/SendTransactionModalLink"; import { OpenWalletInfoFn } from "./info/WalletInfoModal"; -import { keyedNullSort, localeSort } from "@utils"; +import { keyedNullSort } from "@utils"; import { useDateColumnWidth } from "@utils/table"; interface Props { @@ -24,22 +26,127 @@ openWalletInfo: OpenWalletInfoFn; } +function getColumns( + tStr: TStrFn, + categories: string[], + dateColumnWidth: number, + openEditWallet: OpenEditWalletFn, + openSendTx: OpenSendTxFn, + openWalletInfo: OpenWalletInfoFn +): ColumnsType { + return [ + // Label + { + title: tStr("columnLabel"), + dataIndex: "label", key: "label", + + render: (label, record) => <> + {label} + {record.dontSave && + {tStr("tagDontSave")} + } + , + sorter: keyedNullSort("label", true) + }, + + // Address + { + title: tStr("columnAddress"), + dataIndex: "address", key: "address", + + render: (address, wallet) => ( + + ), + sorter: (a, b) => a.address.localeCompare(b.address) + }, + + // Balance + { + title: tStr("columnBalance"), + dataIndex: "balance", key: "balance", + + width: 140, + render: balance => , + sorter: keyedNullSort("balance"), + defaultSortOrder: "descend" + }, + + // Names + { + title: tStr("columnNames"), + dataIndex: "names", key: "names", + sorter: keyedNullSort("names"), + width: 70, + }, + + // Category + { + title: tStr("columnCategory"), + dataIndex: "category", key: "category", + + filters: categories.map(c => ({ text: c, value: c })), + onFilter: (value, record) => record.category === value, + + sorter: keyedNullSort("category", true) + }, + + // First seen + { + title: tStr("columnFirstSeen"), + dataIndex: "firstSeen", key: "firstSeen", + + // This column isn't too important, hide it on smaller screens + responsive: ["lg"], + + render: firstSeen => , + width: dateColumnWidth, + + sorter: keyedNullSort("firstSeen") + }, + + // Actions + { + key: "actions", + width: 80, + + render: (_, record) => ( + + ) + } + ]; +} + export function WalletsTable({ openEditWallet, openSendTx, openWalletInfo }: Props): JSX.Element { - const { t } = useTranslation(); - const { wallets } = useWallets(); + const { tStr } = useTFns("myWallets."); - // Required to filter by categories - const categories = [...new Set(Object.values(wallets) - .filter(w => w.category !== undefined && w.category !== "") - .map(w => w.category) as string[])]; - localeSort(categories); + const { wallets } = useWallets(); + const { categories, joinedCategoryList } = useWalletCategories(); const dateColumnWidth = useDateColumnWidth(); + const columns = useMemo(() => getColumns( + tStr, categories, dateColumnWidth, openEditWallet, openSendTx, + openWalletInfo + // eslint-disable-next-line react-hooks/exhaustive-deps + ), [ + tStr, joinedCategoryList, dateColumnWidth, openEditWallet, openSendTx, + openWalletInfo + ]); + return
<> - {label} - {record.dontSave && - {t("myWallets.tagDontSave")} - } - , - sorter: keyedNullSort("label", true) - }, - - // Address - { - title: t("myWallets.columnAddress"), - dataIndex: "address", key: "address", - - render: (address, wallet) => ( - - ), - sorter: (a, b) => a.address.localeCompare(b.address) - }, - - // Balance - { - title: t("myWallets.columnBalance"), - dataIndex: "balance", key: "balance", - - render: balance => , - sorter: keyedNullSort("balance"), - defaultSortOrder: "descend" - }, - - // Names - { - title: t("myWallets.columnNames"), - dataIndex: "names", key: "names", - sorter: keyedNullSort("names") - }, - - // Category - { - title: t("myWallets.columnCategory"), - dataIndex: "category", key: "category", - - filters: categories.map(c => ({ text: c, value: c })), - onFilter: (value, record) => record.category === value, - - sorter: keyedNullSort("category", true) - }, - - // First seen - { - title: t("myWallets.columnFirstSeen"), - dataIndex: "firstSeen", key: "firstSeen", - - // This column isn't too important, hide it on smaller screens - responsive: ["lg"], - - render: firstSeen => , - width: dateColumnWidth, - - sorter: keyedNullSort("firstSeen") - }, - - // Actions - { - key: "actions", - width: 80, - - render: (_, record) => ( - - ) - } - ]} + columns={columns} />; } diff --git a/src/utils/i18n/fns.ts b/src/utils/i18n/fns.ts index 5573908..9a803c7 100644 --- a/src/utils/i18n/fns.ts +++ b/src/utils/i18n/fns.ts @@ -1,7 +1,7 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under AGPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { useCallback } from "react"; +import { useCallback, useMemo } from "react"; import { i18n } from "i18next"; import { useTranslation, TFunction } from "react-i18next"; import { TranslatedError } from "./errors"; @@ -22,6 +22,8 @@ const tStr = useCallback((key: string) => t(tKey(key)), [t, tKey]); const tErr = useCallback((key: string) => new TranslatedError(tKey(key)), [tKey]); - return { t, tKey, tStr, tErr, i18n }; + return useMemo(() => ({ t, tKey, tStr, tErr, i18n }), [ + t, i18n, tKey, tStr, tErr + ]); } export const useTFns = useTranslationFns;