ujh/fountainpencompanion

View on GitHub
app/javascript/src/collected_inks/table/CollectedInksTable.jsx

Summary

Maintainability
D
2 days
Test Coverage
import React, { useEffect, useMemo } from "react";
import { useTable, useSortBy, useGlobalFilter } from "react-table";
import _ from "lodash";
import { RelativeDate } from "../../components/RelativeDate";
import { useHiddenFields } from "../../useHiddenFields";
import { Actions } from "../components";
import { fuzzyMatch } from "./match";
import { Counter } from "./Counter";
import { InkWithLink } from "./InkWithLink";
import { Table } from "../../components/Table";
import { booleanSort, colorSort } from "./sort";
import { ActionsCell } from "./ActionsCell";

export const storageKeyHiddenFields = "fpc-collected-inks-table-hidden-fields";

function fixedEncodeURIComponent(str) {
  return encodeURIComponent(str).replace(/[!'()*]/g, function (c) {
    return "%" + c.charCodeAt(0).toString(16);
  });
}

export const CollectedInksTable = ({ data, archive, onLayoutChange }) => {
  const columns = useMemo(
    () => [
      {
        accessor: "private",
        Cell: ({ cell: { value } }) => {
          if (value) {
            return (
              <i
                title="Private, hidden from your profile"
                className="fa fa-lock"
              />
            );
          } else {
            return (
              <i
                title="Publicly visible on your profile"
                className="fa fa-unlock"
              />
            );
          }
        }
      },
      {
        Header: "Brand",
        accessor: "brand_name",
        Footer: (info) => {
          const count = useMemo(() => {
            return _.uniqBy(info.rows, (row) => row.values["brand_name"])
              .length;
          }, [info.rows]);
          return <span>{count} brands</span>;
        }
      },
      {
        Header: "Line",
        accessor: "line_name"
      },
      {
        Header: "Name",
        accessor: "ink_name",
        Cell: InkWithLink,
        Footer: (info) => {
          return <span>{info.rows.length} inks</span>;
        }
      },
      {
        Header: "Maker",
        accessor: "maker"
      },
      {
        Header: "Type",
        accessor: "kind",
        Footer: (info) => {
          const counters = useMemo(() => {
            return _.countBy(info.rows, (row) => row.values["kind"]);
          }, [info.rows]);
          return (
            <span>
              <Counter data={counters} field="bottle" />
              <Counter data={counters} field="sample" />
              <Counter data={counters} field="cartridge" />
              <Counter data={counters} field="swab" />
            </span>
          );
        }
      },
      {
        Header: "Color",
        accessor: "color",
        Cell: ({ cell: { value } }) => (
          <div
            style={{
              backgroundColor: value,
              width: "45px",
              height: "45px"
            }}
          ></div>
        ),
        sortType: colorSort
      },
      {
        Header: "Swabbed",
        accessor: "swabbed",
        Cell: ({ cell: { value } }) => {
          if (value) {
            return <i className="fa fa-check" />;
          } else {
            return <i className="fa fa-times" />;
          }
        },
        sortType: booleanSort
      },
      {
        Header: "Used",
        accessor: "used",
        Cell: ({ cell: { value } }) => {
          if (value) {
            return <i className="fa fa-check" />;
          } else {
            return <i className="fa fa-times" />;
          }
        },
        sortType: booleanSort
      },
      {
        Header: "Usage",
        accessor: "usage",
        sortDescFirst: true
      },
      {
        Header: "Daily Usage",
        accessor: "daily_usage",
        sortDescFirst: true
      },
      {
        Header: "Last Usage",
        accessor: "last_used_on",
        sortDescFirst: true,
        Cell: ({ cell: { value } }) => <RelativeDate date={value} />
      },
      {
        Header: "Added On",
        accessor: "created_at",
        Cell: ({ cell: { value } }) => (
          <RelativeDate date={value} relativeAsDefault={false} />
        )
      },
      {
        Header: "Comment",
        accessor: "comment"
      },
      {
        Header: "Private Comment",
        accessor: "private_comment"
      },
      {
        Header: "Tags",
        accessor: "tags",
        Cell: ({ cell: { value } }) => {
          if (!value.length) return null;
          return (
            <ul className="tags">
              {value.map((tag) => (
                <li key={tag.id} className="tag badge text-bg-secondary">
                  <a href={`/inks?tag=${fixedEncodeURIComponent(tag.name)}`}>
                    {tag.name}
                  </a>
                </li>
              ))}
            </ul>
          );
        }
      },
      {
        Header: "Cluster Tags",
        accessor: "cluster_tags",
        Cell: ({
          cell: { value },
          row: {
            values: { tags }
          }
        }) => {
          if (!value.length) return null;
          const clusterOnlyTags = _.difference(
            value,
            tags.map((t) => t.name)
          );
          return (
            <ul className="tags">
              {clusterOnlyTags.map((tag) => (
                <li
                  key={tag}
                  className="tag badge text-bg-secondary cluster-tag"
                >
                  <a href={`/inks?tag=${fixedEncodeURIComponent(tag)}`}>
                    {tag}
                  </a>
                </li>
              ))}
            </ul>
          );
        }
      },
      {
        Header: "Actions",
        Cell: ({ cell: { row } }) => {
          return <ActionsCell {...row.original} id={row.original.id} />;
        }
      }
    ],
    []
  );

  const defaultHiddenFields = useMemo(() => {
    let hideIfNoInksWithValue = [
      "private",
      "private_comment",
      "comment",
      "maker",
      "line_name",
      "kind",
      "daily_usage",
      "last_used_on"
    ].filter((n) => !data.some((e) => e[n]));

    if (data.every((e) => e.tags.length == 0)) {
      hideIfNoInksWithValue.push("tags");
    }
    if (data.every((e) => e.cluster_tags.length == 0)) {
      hideIfNoInksWithValue.push("cluster_tags");
    }
    return hideIfNoInksWithValue;
  }, [data]);

  const { hiddenFields, onHiddenFieldsChange } = useHiddenFields(
    storageKeyHiddenFields,
    defaultHiddenFields
  );

  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    footerGroups,
    rows,
    prepareRow,
    preGlobalFilteredRows,
    setGlobalFilter,
    setHiddenColumns
  } = useTable(
    {
      columns,
      data,
      initialState: {
        hiddenColumns: hiddenFields
      },
      filterTypes: {
        fuzzyText: fuzzyMatch
      },
      globalFilter: "fuzzyText"
    },
    useGlobalFilter,
    useSortBy
  );

  useEffect(() => {
    setHiddenColumns(hiddenFields);
  }, [hiddenFields, setHiddenColumns]);

  return (
    <div>
      <Actions
        archive={archive}
        activeLayout="table"
        numberOfInks={preGlobalFilteredRows.length}
        onFilterChange={setGlobalFilter}
        onLayoutChange={onLayoutChange}
        hiddenFields={hiddenFields}
        onHiddenFieldsChange={onHiddenFieldsChange}
      />
      <Table
        hiddenFields={hiddenFields}
        getTableProps={getTableProps}
        headerGroups={headerGroups}
        getTableBodyProps={getTableBodyProps}
        rows={rows}
        prepareRow={prepareRow}
        footerGroups={footerGroups}
      />
    </div>
  );
};