mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
improve table empty & loading states
This commit is contained in:
@@ -4,6 +4,8 @@ import { alpha } from "@mui/material/styles";
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
import { getFieldProp } from "@src/components/fields";
|
||||
|
||||
export const COLUMN_HEADER_HEIGHT = 42;
|
||||
|
||||
export interface IColumnProps extends Partial<GridProps> {
|
||||
label: string;
|
||||
type?: FieldType;
|
||||
@@ -29,7 +31,7 @@ export default function Column({
|
||||
sx={[
|
||||
{
|
||||
width: "100%",
|
||||
height: 42,
|
||||
height: COLUMN_HEADER_HEIGHT,
|
||||
border: (theme) => `1px solid ${theme.palette.divider}`,
|
||||
backgroundColor: "background.default",
|
||||
|
||||
|
||||
@@ -26,10 +26,12 @@ import {
|
||||
import { tableScope, updateColumnAtom } from "@src/atoms/tableScope";
|
||||
import { FieldType } from "@src/constants/fields";
|
||||
import { getFieldProp } from "@src/components/fields";
|
||||
import { DEFAULT_ROW_HEIGHT } from "@src/components/Table";
|
||||
import { COLUMN_HEADER_HEIGHT } from "@src/components/Table/Column";
|
||||
import { ColumnConfig } from "@src/types/table";
|
||||
import useKeyPress from "@src/hooks/useKeyPress";
|
||||
|
||||
export { COLUMN_HEADER_HEIGHT };
|
||||
|
||||
const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props} classes={{ popper: className }} />
|
||||
))(({ theme }) => ({
|
||||
@@ -37,7 +39,7 @@ const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
|
||||
margin: `-${DEFAULT_ROW_HEIGHT - 2}px 0 0 !important`,
|
||||
margin: `-${COLUMN_HEADER_HEIGHT - 1 - 2}px 0 0 !important`,
|
||||
padding: 0,
|
||||
paddingRight: theme.spacing(1.5),
|
||||
},
|
||||
@@ -83,6 +85,7 @@ export default function DraggableHeaderRenderer({
|
||||
|
||||
return (
|
||||
<Grid
|
||||
key={column.key}
|
||||
ref={(ref) => {
|
||||
dragRef(ref);
|
||||
dropRef(ref);
|
||||
@@ -164,7 +167,7 @@ export default function DraggableHeaderRenderer({
|
||||
sx={{
|
||||
typography: "caption",
|
||||
fontWeight: "fontWeightMedium",
|
||||
lineHeight: `${DEFAULT_ROW_HEIGHT - 1 - 4}px`,
|
||||
lineHeight: `${COLUMN_HEADER_HEIGHT - 2 - 4}px`,
|
||||
textOverflow: "clip",
|
||||
}}
|
||||
color="inherit"
|
||||
@@ -182,7 +185,7 @@ export default function DraggableHeaderRenderer({
|
||||
sx={{
|
||||
typography: "caption",
|
||||
fontWeight: "fontWeightMedium",
|
||||
lineHeight: `${DEFAULT_ROW_HEIGHT + 1}px`,
|
||||
lineHeight: `${COLUMN_HEADER_HEIGHT}px`,
|
||||
textOverflow: "clip",
|
||||
}}
|
||||
component="div"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useAtom } from "jotai";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
|
||||
import { Grid, Stack, Typography, Button, Divider } from "@mui/material";
|
||||
import ImportIcon from "@src/assets/icons/Import";
|
||||
import AddColumnIcon from "@src/assets/icons/AddColumn";
|
||||
|
||||
import { globalScope, columnModalAtom } from "@src/atoms/globalScope";
|
||||
import {
|
||||
tableScope,
|
||||
tableSettingsAtom,
|
||||
@@ -17,6 +18,8 @@ import { APP_BAR_HEIGHT } from "@src/layouts/Navigation";
|
||||
// import ImportCSV from "@src/components/TableToolbar/ImportCsv";
|
||||
|
||||
export default function EmptyTable() {
|
||||
const openColumnModal = useSetAtom(columnModalAtom, globalScope);
|
||||
|
||||
const [tableSettings] = useAtom(tableSettingsAtom, tableScope);
|
||||
const [tableRows] = useAtom(tableRowsAtom, tableScope);
|
||||
// const { tableState, importWizardRef, columnMenuRef } = useProjectContext();
|
||||
@@ -115,19 +118,10 @@ export default function EmptyTable() {
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<AddColumnIcon />}
|
||||
// onClick={(event) =>
|
||||
// columnMenuRef?.current?.setSelectedColumnHeader({
|
||||
// column: { isNew: true, key: "new", type: "LAST" } as any,
|
||||
// anchorEl: event.currentTarget,
|
||||
// })
|
||||
// }
|
||||
// disabled={!columnMenuRef?.current}
|
||||
disabled
|
||||
onClick={() => openColumnModal({ type: "new" })}
|
||||
>
|
||||
Add column
|
||||
</Button>
|
||||
|
||||
{/* <ColumnMenu /> */}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React, { useRef, useMemo, useState } from "react";
|
||||
import { find, difference, get } from "lodash-es";
|
||||
import { find, difference } from "lodash-es";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
|
||||
@@ -11,14 +10,18 @@ import DataGrid, {
|
||||
Column,
|
||||
// SelectColumn as _SelectColumn,
|
||||
} from "react-data-grid";
|
||||
import { LinearProgress } from "@mui/material";
|
||||
|
||||
import TableContainer, { OUT_OF_ORDER_MARGIN } from "./TableContainer";
|
||||
import ColumnHeader from "./ColumnHeader";
|
||||
import ColumnHeader, { COLUMN_HEADER_HEIGHT } from "./ColumnHeader";
|
||||
// import ContextMenu from "./ContextMenu";
|
||||
import FinalColumnHeader from "./FinalColumnHeader";
|
||||
import FinalColumn from "./formatters/FinalColumn";
|
||||
import TableRow from "./TableRow";
|
||||
import EmptyState from "@src/components/EmptyState";
|
||||
// import BulkActions from "./BulkActions";
|
||||
import AddRow from "@src/components/TableToolbar/AddRow";
|
||||
import AddRowIcon from "@src/assets/icons/AddRow";
|
||||
|
||||
import {
|
||||
globalScope,
|
||||
@@ -152,7 +155,7 @@ export default function Table() {
|
||||
// { ...row }
|
||||
// )
|
||||
// );
|
||||
}, [columns, tableRows]) ?? [];
|
||||
}, [tableRows]) ?? [];
|
||||
|
||||
const rowsContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [selectedRowsSet, setSelectedRowsSet] = useState<Set<React.Key>>();
|
||||
@@ -166,8 +169,6 @@ export default function Table() {
|
||||
const isAtBottom =
|
||||
target.clientHeight + target.scrollTop >= target.scrollHeight - offset;
|
||||
if (!isAtBottom) return;
|
||||
// Prevent calling more rows when they’ve already been called
|
||||
if (tableLoadingMore) return;
|
||||
// Call for the next page
|
||||
setTablePageAtom((p) => p + 1);
|
||||
};
|
||||
@@ -271,6 +272,26 @@ export default function Table() {
|
||||
// }
|
||||
/>
|
||||
</DndProvider>
|
||||
|
||||
{tableRows.length === 0 && (
|
||||
<EmptyState
|
||||
Icon={AddRowIcon}
|
||||
message="Add a row to get started"
|
||||
description={
|
||||
<div>
|
||||
<br />
|
||||
<AddRow />
|
||||
</div>
|
||||
}
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: 0,
|
||||
top: COLUMN_HEADER_HEIGHT,
|
||||
height: "auto",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{tableLoadingMore && <LinearProgress />}
|
||||
</TableContainer>
|
||||
|
||||
{/* <ContextMenu />
|
||||
|
||||
@@ -13,6 +13,7 @@ export const TableContainer = styled("div", {
|
||||
shouldForwardProp: (prop) => prop !== "rowHeight",
|
||||
})<{ rowHeight: number }>(({ theme, rowHeight }) => ({
|
||||
display: "flex",
|
||||
position: "relative",
|
||||
flexDirection: "column",
|
||||
height: `calc(100vh - ${APP_BAR_HEIGHT}px - ${TABLE_TOOLBAR_HEIGHT}px)`,
|
||||
|
||||
|
||||
@@ -85,7 +85,6 @@ export default function Filters() {
|
||||
|
||||
// Set the local table filter
|
||||
useEffect(() => {
|
||||
console.log("Filters effect");
|
||||
// Set local state for UI
|
||||
setTableQuery(
|
||||
Array.isArray(tableFilters) && tableFilters[0]
|
||||
|
||||
@@ -180,6 +180,8 @@ export default function ImportCsv({ render, PopoverProps }: IImportCsvProps) {
|
||||
title="Import CSV or TSV"
|
||||
onClick={handleOpen}
|
||||
icon={<ImportIcon />}
|
||||
// FIXME:
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Suspense, forwardRef } from "react";
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
import { Tooltip, Typography } from "@mui/material";
|
||||
import { Tooltip, Typography, TypographyProps } from "@mui/material";
|
||||
|
||||
import {
|
||||
tableScope,
|
||||
@@ -10,7 +11,23 @@ import {
|
||||
} from "@src/atoms/tableScope";
|
||||
import { COLLECTION_PAGE_SIZE } from "@src/config/db";
|
||||
|
||||
export default function LoadedRowsStatus() {
|
||||
const StatusText = forwardRef(function StatusText(
|
||||
props: TypographyProps,
|
||||
ref: React.Ref<HTMLButtonElement>
|
||||
) {
|
||||
return (
|
||||
<Typography
|
||||
ref={ref}
|
||||
variant="body2"
|
||||
color="text.disabled"
|
||||
display="block"
|
||||
{...props}
|
||||
style={{ userSelect: "none", ...props.style }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
function LoadedRowsStatus() {
|
||||
const [tableRows] = useAtom(tableRowsAtom, tableScope);
|
||||
const [tableLoadingMore] = useAtom(tableLoadingMoreAtom, tableScope);
|
||||
const [tablePage] = useAtom(tablePageAtom, tableScope);
|
||||
@@ -18,23 +35,28 @@ export default function LoadedRowsStatus() {
|
||||
const allLoaded =
|
||||
!tableLoadingMore && tableRows.length < COLLECTION_PAGE_SIZE * tablePage;
|
||||
|
||||
if (tableLoadingMore) return <StatusText>Loading more…</StatusText>;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
allLoaded
|
||||
? "All rows have been loaded in this table"
|
||||
: `Scroll to the bottom to load more rows`
|
||||
: "Scroll to the bottom to load more rows"
|
||||
}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.disabled"
|
||||
display="block"
|
||||
style={{ userSelect: "none" }}
|
||||
>
|
||||
<StatusText>
|
||||
Loaded {allLoaded && "all "}
|
||||
{tableRows.length} row{tableRows.length !== 1 && "s"}
|
||||
</Typography>
|
||||
</StatusText>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SuspendedLoadedRowsStatus() {
|
||||
return (
|
||||
<Suspense fallback={<StatusText>Loading…</StatusText>}>
|
||||
<LoadedRowsStatus />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,6 +130,7 @@ export function useFirestoreCollectionWithAtom<T = TableRow>(
|
||||
isLastPage || queryEqual(next?.query as any, prev?.query as any)
|
||||
);
|
||||
|
||||
// Create listener
|
||||
useEffect(() => {
|
||||
// If path is invalid and no memoizedQuery was created, don’t continue
|
||||
if (!memoizedQuery) return;
|
||||
@@ -244,7 +245,6 @@ export function useFirestoreCollectionWithAtom<T = TableRow>(
|
||||
setUpdateDocAtom,
|
||||
deleteDocAtom,
|
||||
setDeleteDocAtom,
|
||||
loadingMoreAtom,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,11 +28,17 @@ function TablePage() {
|
||||
|
||||
if (isEmpty(tableSchema.columns))
|
||||
return (
|
||||
<Fade style={{ transitionDelay: "500ms" }}>
|
||||
<div>
|
||||
<EmptyTable />
|
||||
</div>
|
||||
</Fade>
|
||||
<Suspense fallback={null}>
|
||||
<Fade in style={{ transitionDelay: "500ms" }}>
|
||||
<div>
|
||||
<EmptyTable />
|
||||
|
||||
<Suspense fallback={null}>
|
||||
<ColumnModals />
|
||||
</Suspense>
|
||||
</div>
|
||||
</Fade>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -45,7 +51,7 @@ function TablePage() {
|
||||
<Table />
|
||||
</Suspense>
|
||||
|
||||
<Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<ColumnMenu />
|
||||
<ColumnModals />
|
||||
</Suspense>
|
||||
|
||||
@@ -1224,7 +1224,7 @@ export const components = (theme: Theme): ThemeOptions => {
|
||||
MuiAvatar: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
...theme.typography.button,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
},
|
||||
colorDefault: {
|
||||
backgroundColor: theme.palette.action.selected,
|
||||
|
||||
Reference in New Issue
Block a user