Skip to content

Commit

Permalink
✨ feat(data): allow creating new data on empty views
Browse files Browse the repository at this point in the history
  • Loading branch information
thrownullexception committed Jan 2, 2024
1 parent 05de64f commit 9b98310
Show file tree
Hide file tree
Showing 25 changed files with 172 additions and 102 deletions.
7 changes: 4 additions & 3 deletions src/frontend/components/FEPaginationTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TableSkeleton } from "frontend/design-system/components/Skeleton/Table"
import { Table } from "frontend/design-system/components/Table";
import { ITableColumn } from "frontend/design-system/components/Table/types";
import { TableFilterType } from "frontend/design-system/components/Table/filters/types";
import { IEmptyWrapperProps } from "frontend/design-system/components/EmptyWrapper/types";
import { useFEPagination } from "./useFEPagination";
import { ViewStateMachine } from "../ViewStateMachine";

Expand All @@ -26,14 +27,14 @@ export interface IFETableColumn<T extends Record<string, unknown>> {
interface IProps<T extends Record<string, unknown>> {
columns: IFETableColumn<T>[];
dataEndpoint: string;
emptyMessage: string;
empty: IEmptyWrapperProps;
border?: true;
}

export function FEPaginationTable<T extends Record<string, unknown>>({
columns,
dataEndpoint,
emptyMessage,
empty,
border,
}: IProps<T>) {
const [paginatedDataState, setPaginatedDataState] =
Expand All @@ -59,7 +60,7 @@ export function FEPaginationTable<T extends Record<string, unknown>>({
overridePaginatedDataState: DEFAULT_TABLE_STATE,
}}
border={border}
emptyMessage={emptyMessage}
empty={empty}
columns={columns as ITableColumn[]}
/>
</ViewStateMachine>
Expand Down
19 changes: 10 additions & 9 deletions src/frontend/design-system/components/EmptyWrapper/Stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import React from "react";
import { Story } from "@storybook/react";
import { ApplicationRoot } from "frontend/components/ApplicationRoot";
import { EmptyWrapper, IProps } from ".";
import { noop } from "shared/lib/noop";
import { EmptyWrapper } from ".";
import { IEmptyWrapperProps } from "./types";

export default {
title: "Components/EmptyWrapper",
Expand All @@ -12,7 +14,7 @@ export default {
},
};

const Template: Story<IProps> = (args) => (
const Template: Story<IEmptyWrapperProps> = (args) => (
<ApplicationRoot>
<EmptyWrapper {...args} />
</ApplicationRoot>
Expand All @@ -21,11 +23,10 @@ const Template: Story<IProps> = (args) => (
export const Default = Template.bind({});
Default.args = {};

export const WithChildren = Template.bind({});
WithChildren.args = {
children: (
<>
<b>This is bold</b> This is not
</>
),
export const WithCreateNew = Template.bind({});
WithCreateNew.args = {
createNew: {
label: "Add New Item",
action: () => noop(),
},
};
26 changes: 16 additions & 10 deletions src/frontend/design-system/components/EmptyWrapper/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import React, { ReactNode } from "react";
import React from "react";
import styled from "styled-components";
import { Frown as Droplet } from "react-feather";
import { USE_ROOT_COLOR } from "frontend/design-system/theme/root";
import { Typo } from "frontend/design-system/primitives/Typo";

export interface IProps {
text: string;
children?: ReactNode;
}
import { Spacer } from "frontend/design-system/primitives/Spacer";
import { SoftButton } from "../Button/SoftButton";
import { IEmptyWrapperProps } from "./types";

const Root = styled.div`
text-align: center;
Expand All @@ -17,14 +15,22 @@ const Root = styled.div`
background: ${USE_ROOT_COLOR("base-color")};
`;

export function EmptyWrapper({ text, children }: IProps) {
export function EmptyWrapper({ text, createNew }: IEmptyWrapperProps) {
return (
<Root>
<Droplet size={50} color={USE_ROOT_COLOR("muted-text")} />
<br />
<br />
<Spacer size="xxl" />
<Typo.MD color="muted"> {text} </Typo.MD>
{children}
{createNew && (
<>
<Spacer size="xxl" />
<SoftButton
action={createNew.action}
icon="add"
label={createNew.label}
/>
</>
)}
</Root>
);
}
4 changes: 4 additions & 0 deletions src/frontend/design-system/components/EmptyWrapper/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IEmptyWrapperProps {
text: string;
createNew?: { action: string | (() => void); label: string };
}
19 changes: 3 additions & 16 deletions src/frontend/design-system/components/ListManager/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React, { ReactNode, useState } from "react";
import { Spacer } from "frontend/design-system/primitives/Spacer";
import styled from "styled-components";
import { DataStateKeys } from "frontend/lib/data/types";
import { ViewStateMachine } from "frontend/components/ViewStateMachine";
import { EmptyWrapper } from "../EmptyWrapper";
import { FormSearch } from "../Form/FormSearch";
import { SoftButton } from "../Button/SoftButton";
import { defaultSearchFunction } from "./utils";
import { ListSkeleton } from "../Skeleton/List";
import { IEmptyWrapperProps } from "../EmptyWrapper/types";

export { ListManagerItem } from "./ListManagerItem";

Expand All @@ -31,10 +30,7 @@ export interface IProps<T, K extends StringProps<T>> {
items: DataStateKeys<T[]>;
listLengthGuess: number;
labelField: K;
empty?: {
text: string;
createNew?: { action: string | (() => void); label: string };
};
empty?: IEmptyWrapperProps;
getLabel?: (name: string) => string;
render: (item: T & { label: string }, index: number) => ReactNode;
}
Expand Down Expand Up @@ -71,16 +67,7 @@ export function ListManager<T, K extends StringProps<T>>({
loader={<ListSkeleton count={listLengthGuess} />}
>
{itemsLength === 0 ? (
<EmptyWrapper text={empty?.text}>
<Spacer size="sm" />
{empty?.createNew && (
<SoftButton
action={empty.createNew.action}
icon="add"
label={empty.createNew.label}
/>
)}
</EmptyWrapper>
<EmptyWrapper {...{ ...empty }} />
) : (
<Root>
{itemsLength > SEARCH_THRESHOLD ? (
Expand Down
12 changes: 4 additions & 8 deletions src/frontend/design-system/components/Table/Body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { USE_ROOT_COLOR } from "frontend/design-system/theme/root";
import { useThemeColorShade } from "frontend/design-system/theme/useTheme";
import { Typo } from "frontend/design-system/primitives/Typo";
import { EmptyWrapper } from "../EmptyWrapper";
import { IEmptyWrapperProps } from "../EmptyWrapper/types";

const Td = styled.td`
padding: 0.45rem;
Expand All @@ -29,15 +30,10 @@ interface IProps {
table: Table<Record<string, unknown>>;
dataLength: number;
isLoading: boolean;
emptyMessage?: string;
empty: IEmptyWrapperProps;
}

export function TableBody({
table,
dataLength,
emptyMessage,
isLoading,
}: IProps) {
export function TableBody({ table, dataLength, empty, isLoading }: IProps) {
const colorShade = useThemeColorShade();
return (
<tbody>
Expand All @@ -58,7 +54,7 @@ export function TableBody({
{isLoading ? (
<div style={{ height: "204px" }} />
) : (
<EmptyWrapper text={emptyMessage || "No Data"} />
<EmptyWrapper {...{ ...empty }} />
)}
</Td>
</BodyTR>
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/design-system/components/Table/Stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export default {
},
syncPaginatedDataStateOut: action("setPaginatedDataState"),
columns: TABLE_COLUMNS,
empty: {
text: "Empty Table",
},
tableData: TABLE_DATA,
} as ITableProps<unknown>,
};
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/design-system/components/Table/Table.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ const DEFAULT_TABLE_PROPS: ITableProps<unknown> = {
},
syncPaginatedDataStateOut: jest.fn(),
columns: TABLE_COLUMNS,
empty: {
text: "Empty Table",
},
tableData: TABLE_DATA,
};

Expand Down
4 changes: 2 additions & 2 deletions src/frontend/design-system/components/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function Table<T extends unknown>({
columns,
lean,
border,
emptyMessage,
empty,
}: ITableProps<T>) {
const {
data = {
Expand Down Expand Up @@ -138,7 +138,7 @@ export function Table<T extends unknown>({
<TableBody
table={table}
dataLength={dataLength}
emptyMessage={emptyMessage}
empty={empty}
isLoading={isLoading}
/>
<TableFoot table={table} dataLength={dataLength} />
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/design-system/components/Table/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ReactNode } from "react";
import { UseQueryResult } from "react-query";
import { IPaginatedDataState, PaginatedData } from "shared/types/data";
import { TableFilterType } from "./filters/types";
import { IEmptyWrapperProps } from "../EmptyWrapper/types";

export interface ITableColumn {
Header:
Expand All @@ -29,5 +30,5 @@ export interface ITableProps<T> {
border?: boolean;
overridePaginatedDataState?: IPaginatedDataState<T>;
syncPaginatedDataStateOut: (params: IPaginatedDataState<T>) => void;
emptyMessage?: string;
empty: IEmptyWrapperProps;
}
2 changes: 1 addition & 1 deletion src/frontend/views/Dashboard/Skeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function DashboardSkeleton() {
<BaseSkeleton height="100px" />
</Root>
<Root>
<WidgetRoot $span="4" $height="3" hasSetting={false}>
<WidgetRoot $span="4" $height="3">
<TableSkeleton />
</WidgetRoot>
</Root>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function TableWidget({ data }: IProps) {
}}
syncPaginatedDataStateOut={() => {}}
border
empty={{ text: "No Data" }}
lean
columns={columns}
/>
Expand Down
11 changes: 9 additions & 2 deletions src/frontend/views/data/Details/RelationsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import {
} from "frontend/hooks/entity/entity.config";
import { ENTITY_DETAILS_VIEW_KEY } from "./constants";
import { DetailsLayout } from "./_Layout";
import { useTableMenuItems } from "../Table/useTableMenuItems";
import {
getEntityCreateLink,
useTableMenuItems,
} from "../Table/useTableMenuItems";
import { WholeEntityTable } from "../Table/_WholeEntityTable";

export function EntityRelationTable() {
Expand Down Expand Up @@ -55,7 +58,11 @@ export function EntityRelationTable() {
<WholeEntityTable
entity={childEntity}
skipColumns={referenceField ? [referenceField] : []}
persistFilters={
createNewLink={getEntityCreateLink(childEntity, {
referenceField,
entityId,
})}
persistentFilters={
referenceField
? [
{
Expand Down
29 changes: 16 additions & 13 deletions src/frontend/views/data/Table/DataTable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import { ICrudConfig } from "frontend/lib/crud-config";
import { usePaginatedData } from "frontend/lib/data/useApi/usePaginatedData";
import { DEFAULT_PAGINATED_DATA } from "frontend/lib/data/constants/defaults";
import { pluralize } from "shared/lib/strings";
import { ITableColumn } from "frontend/design-system/components/Table/types";
import { Table } from "frontend/design-system/components/Table";
import { IEmptyWrapperProps } from "frontend/design-system/components/EmptyWrapper/types";
import { useTableState } from "../hooks";
import { IDataTableProps } from "../types";

interface IProps extends IDataTableProps {
columns: ITableColumn[];
dataEndpoint: string;
stateStorageKey: string;
crudConfig: ICrudConfig;
empty: IEmptyWrapperProps;
}

export function BaseDataTable({
columns,
stateStorageKey,
dataEndpoint,
crudConfig,
empty,
skipColumns = [],
border,
persitentFilters = [],
persistentFilters = [],
defaultTableState,
}: IProps) {
const [currentState, overridePaginatedDataState, setPaginatedDataState] =
useTableState(stateStorageKey, persitentFilters, defaultTableState);
useTableState(stateStorageKey, persistentFilters, defaultTableState);

const tableData = usePaginatedData(dataEndpoint, currentState, {
defaultData: DEFAULT_PAGINATED_DATA,
Expand All @@ -39,15 +39,18 @@ export function BaseDataTable({
overridePaginatedDataState,
}}
border={border}
emptyMessage={
currentState.filters.length > 0
empty={
currentState.filters.length > 0 &&
persistentFilters.length !== currentState.filters.length
? // TODO: for contributors: transform this to user readable message
`No result for the current ${pluralize({
singular: "filter",
count: currentState.filters.length,
inclusive: true,
})} applied.`
: crudConfig.TEXT_LANG.EMPTY_LIST
{
text: `No result for the current ${pluralize({
singular: "filter",
count: currentState.filters.length,
inclusive: true,
})} applied.`,
}
: empty
}
columns={columns.filter(
(column) => !skipColumns.includes(column.accessor)
Expand Down
Loading

0 comments on commit 9b98310

Please sign in to comment.