mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
support multiple tables pointing to the same collection
This commit is contained in:
@@ -17,18 +17,19 @@ import routes from "constants/routes";
|
||||
|
||||
export default function Breadcrumbs(props: BreadcrumbsProps) {
|
||||
const { tables, tableState } = useProjectContext();
|
||||
const collection = tableState?.tablePath || "";
|
||||
const id = tableState?.config.id || "";
|
||||
const collection = id || tableState?.tablePath || "";
|
||||
|
||||
const router = useRouter();
|
||||
const parentLabel = decodeURIComponent(
|
||||
let parentLabel = decodeURIComponent(
|
||||
queryString.parse(router.location.search).parentLabel as string
|
||||
);
|
||||
if (parentLabel === "undefined") parentLabel = "";
|
||||
|
||||
const breadcrumbs = collection.split("/");
|
||||
|
||||
const section = _find(tables, ["collection", breadcrumbs[0]])?.section || "";
|
||||
const getLabel = (collection: string) =>
|
||||
_find(tables, ["collection", collection])?.name || collection;
|
||||
const section = _find(tables, ["id", breadcrumbs[0]])?.section || "";
|
||||
const getLabel = (id: string) => _find(tables, ["id", id])?.name || id;
|
||||
|
||||
return (
|
||||
<MuiBreadcrumbs
|
||||
@@ -41,11 +42,6 @@ export default function Breadcrumbs(props: BreadcrumbsProps) {
|
||||
flexWrap: "nowrap",
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
|
||||
"& li": {
|
||||
textTransform: "capitalize",
|
||||
"&:first-of-type": { textTransform: "capitalize" },
|
||||
},
|
||||
}}
|
||||
{...(props as any)}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useLocation } from "react-router-dom";
|
||||
import _find from "lodash/find";
|
||||
import _groupBy from "lodash/groupBy";
|
||||
|
||||
import {
|
||||
Drawer,
|
||||
@@ -21,6 +22,7 @@ import Logo from "assets/Logo";
|
||||
import NavItem from "./NavItem";
|
||||
import NavTableSection from "./NavTableSection";
|
||||
|
||||
import { useAppContext } from "contexts/AppContext";
|
||||
import { useProjectContext } from "contexts/ProjectContext";
|
||||
import { routes } from "constants/routes";
|
||||
|
||||
@@ -35,7 +37,16 @@ export default function NavDrawer({
|
||||
currentSection,
|
||||
...props
|
||||
}: INavDrawerProps) {
|
||||
const { userClaims, sections } = useProjectContext();
|
||||
const { userDoc } = useAppContext();
|
||||
const { userClaims, tables } = useProjectContext();
|
||||
|
||||
const favorites = Array.isArray(userDoc.state.doc?.favoriteTables)
|
||||
? userDoc.state.doc.favoriteTables
|
||||
: [];
|
||||
const sections = {
|
||||
Favorites: favorites.map((id) => _find(tables, { id })),
|
||||
..._groupBy(tables, "section"),
|
||||
};
|
||||
|
||||
const closeDrawer = (e: {}) => props.onClose(e, "escapeKeyDown");
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
import { List, ListItemText, Collapse } from "@material-ui/core";
|
||||
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
||||
@@ -8,6 +9,7 @@ import { Table } from "contexts/ProjectContext";
|
||||
import { routes } from "constants/routes";
|
||||
|
||||
export interface INavDrawerItemProps {
|
||||
open?: boolean;
|
||||
section: string;
|
||||
tables: Table[];
|
||||
currentSection?: string;
|
||||
@@ -15,12 +17,14 @@ export interface INavDrawerItemProps {
|
||||
}
|
||||
|
||||
export default function NavDrawerItem({
|
||||
open: openProp,
|
||||
section,
|
||||
tables,
|
||||
currentSection,
|
||||
closeDrawer,
|
||||
}: INavDrawerItemProps) {
|
||||
const [open, setOpen] = useState(section === currentSection);
|
||||
const { pathname } = useLocation();
|
||||
const [open, setOpen] = useState(openProp || section === currentSection);
|
||||
|
||||
return (
|
||||
<li>
|
||||
@@ -42,27 +46,30 @@ export default function NavDrawerItem({
|
||||
|
||||
<Collapse in={open}>
|
||||
<List disablePadding>
|
||||
{tables.map((table) => (
|
||||
<li key={table.collection}>
|
||||
<NavItem
|
||||
to={
|
||||
table.isCollectionGroup
|
||||
? `${routes.tableGroup}/${table.collection}`
|
||||
: `${routes.table}/${table.collection.replace(
|
||||
/\//g,
|
||||
"~2F"
|
||||
)}`
|
||||
}
|
||||
onClick={closeDrawer}
|
||||
sx={{
|
||||
ml: 2,
|
||||
width: (theme) => `calc(100% - ${theme.spacing(2 + 0.5)})`,
|
||||
}}
|
||||
>
|
||||
<ListItemText primary={table.name} />
|
||||
</NavItem>
|
||||
</li>
|
||||
))}
|
||||
{tables
|
||||
.filter((x) => x)
|
||||
.map((table) => {
|
||||
const route = table.isCollectionGroup
|
||||
? `${routes.tableGroup}/${table.id}`
|
||||
: `${routes.table}/${table.id.replace(/\//g, "~2F")}`;
|
||||
|
||||
return (
|
||||
<li key={table.id}>
|
||||
<NavItem
|
||||
to={route}
|
||||
selected={pathname.split("%2F")[0] === route}
|
||||
onClick={closeDrawer}
|
||||
sx={{
|
||||
ml: 2,
|
||||
width: (theme) =>
|
||||
`calc(100% - ${theme.spacing(2 + 0.5)})`,
|
||||
}}
|
||||
>
|
||||
<ListItemText primary={table.name} />
|
||||
</NavItem>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</Collapse>
|
||||
</li>
|
||||
|
||||
@@ -55,7 +55,13 @@ export default function EmptyTable() {
|
||||
<Typography variant="h6" component="h2" gutterBottom>
|
||||
Get Started
|
||||
</Typography>
|
||||
<Typography>There is no data in this table.</Typography>
|
||||
<Typography>
|
||||
There is no data in the Firestore collection:
|
||||
<br />
|
||||
<Typography component="span" sx={{ fontFamily: "mono" }}>
|
||||
{tableState?.tablePath}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<Grid container spacing={1}>
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function LoadedRowsStatus() {
|
||||
variant="body2"
|
||||
color="text.disabled"
|
||||
display="block"
|
||||
style={{ userSelect: "none", display: "flex", alignItems: "center" }}
|
||||
style={{ userSelect: "none" }}
|
||||
>
|
||||
Loaded {allLoaded && "all "}
|
||||
{tableState.rows.length} rows
|
||||
|
||||
@@ -8,8 +8,6 @@ import { db } from "../../../firebase";
|
||||
import { isCollectionGroup } from "utils/fns";
|
||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||
|
||||
import { DialogContentText } from "@material-ui/core";
|
||||
|
||||
import Modal from "components/Modal";
|
||||
|
||||
export default function ReExecute() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import _find from "lodash/find";
|
||||
|
||||
import TableHeaderButton from "./TableHeaderButton";
|
||||
import SettingsIcon from "@material-ui/icons/SettingsOutlined";
|
||||
@@ -11,7 +12,8 @@ import { useProjectContext } from "contexts/ProjectContext";
|
||||
export default function TableSettings() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { tableState } = useProjectContext();
|
||||
const { tableState, tables } = useProjectContext();
|
||||
const table = _find(tables, { id: tableState?.config.id });
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -19,13 +21,16 @@ export default function TableSettings() {
|
||||
title="Table Settings"
|
||||
onClick={() => setOpen(true)}
|
||||
icon={<SettingsIcon />}
|
||||
disabled={!table}
|
||||
/>
|
||||
|
||||
<TableSettingsDialog
|
||||
clearDialog={() => setOpen(false)}
|
||||
mode={open ? TableSettingsDialogModes.update : null}
|
||||
data={open ? tableState?.config.tableConfig.doc : null}
|
||||
/>
|
||||
{table && (
|
||||
<TableSettingsDialog
|
||||
clearDialog={() => setOpen(false)}
|
||||
mode={open ? TableSettingsDialogModes.update : null}
|
||||
data={open ? table : null}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,12 @@ export const tableSettings = (
|
||||
label: "Table Name",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: FieldType.shortText,
|
||||
name: "id",
|
||||
label: "Table ID",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: FieldType.shortText,
|
||||
name: "collection",
|
||||
|
||||
@@ -10,11 +10,11 @@ import Confirmation from "components/Confirmation";
|
||||
import { FormDialog } from "@antlerengineering/form-builder";
|
||||
import { tableSettings } from "./form";
|
||||
|
||||
import { useProjectContext } from "contexts/ProjectContext";
|
||||
import { useProjectContext, Table } from "contexts/ProjectContext";
|
||||
import useRouter from "../../hooks/useRouter";
|
||||
import { db } from "../../firebase";
|
||||
import { name } from "@root/package.json";
|
||||
import { SETTINGS, TABLE_SCHEMAS } from "config/dbPaths";
|
||||
import { SETTINGS, TABLE_SCHEMAS, TABLE_GROUP_SCHEMAS } from "config/dbPaths";
|
||||
|
||||
export enum TableSettingsDialogModes {
|
||||
create,
|
||||
@@ -23,15 +23,7 @@ export enum TableSettingsDialogModes {
|
||||
export interface ICreateTableDialogProps {
|
||||
mode: TableSettingsDialogModes | null;
|
||||
clearDialog: () => void;
|
||||
data: {
|
||||
name: string;
|
||||
collection: string;
|
||||
tableType: string;
|
||||
section: string;
|
||||
description: string;
|
||||
isCollectionGroup: boolean;
|
||||
roles: string[];
|
||||
} | null;
|
||||
data: Table | null;
|
||||
}
|
||||
|
||||
const FORM_EMPTY_STATE = {
|
||||
@@ -67,8 +59,10 @@ export default function TableSettingsDialog({
|
||||
}: ICreateTableDialogProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
const { settingsActions, sections, roles, tables } = useProjectContext();
|
||||
const sectionNames = sections ? Object.keys(sections) : [];
|
||||
const { settingsActions, roles, tables } = useProjectContext();
|
||||
const sectionNames = Array.from(
|
||||
new Set((tables ?? []).map((t) => t.section))
|
||||
);
|
||||
|
||||
const router = useRouter();
|
||||
const open = mode !== null;
|
||||
@@ -101,22 +95,21 @@ export default function TableSettingsDialog({
|
||||
};
|
||||
|
||||
if (values.schemaSource)
|
||||
data.schemaSource = _find(tables, { collection: values.schemaSource });
|
||||
data.schemaSource = _find(tables, { id: values.schemaSource });
|
||||
|
||||
if (mode === TableSettingsDialogModes.update) {
|
||||
await Promise.all([settingsActions?.updateTable(data), handleClose()]);
|
||||
window.location.reload();
|
||||
} else {
|
||||
settingsActions?.createTable(data);
|
||||
|
||||
if (router.location.pathname === "/") {
|
||||
router.history.push(
|
||||
`${values.tableType === "collectionGroup" ? "tableGroup" : "table"}/${
|
||||
values.collection
|
||||
values.id
|
||||
}`
|
||||
);
|
||||
} else {
|
||||
router.history.push(values.collection);
|
||||
router.history.push(values.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +117,7 @@ export default function TableSettingsDialog({
|
||||
};
|
||||
|
||||
const handleResetStructure = async () => {
|
||||
const schemaDocRef = db.doc(`${TABLE_SCHEMAS}/${data!.collection}`);
|
||||
const schemaDocRef = db.doc(`${TABLE_SCHEMAS}/${data!.id}`);
|
||||
await schemaDocRef.update({ columns: {} });
|
||||
handleClose();
|
||||
};
|
||||
@@ -134,13 +127,13 @@ export default function TableSettingsDialog({
|
||||
const tableData = (await tablesDocRef.get()).data();
|
||||
const updatedTables = tableData?.tables.filter(
|
||||
(table) =>
|
||||
table.collection !== data?.collection ||
|
||||
table.id !== data?.id ||
|
||||
table.isCollectionGroup !== data?.isCollectionGroup
|
||||
);
|
||||
await tablesDocRef.update({ tables: updatedTables });
|
||||
await tablesDocRef
|
||||
.collection(Boolean(data?.isCollectionGroup) ? "schema" : "groupSchema")
|
||||
.doc(data?.collection)
|
||||
await db
|
||||
.collection(data?.isCollectionGroup ? TABLE_SCHEMAS : TABLE_GROUP_SCHEMAS)
|
||||
.doc(data?.id)
|
||||
.delete();
|
||||
window.location.reload();
|
||||
handleClose();
|
||||
@@ -158,7 +151,7 @@ export default function TableSettingsDialog({
|
||||
mode,
|
||||
roles,
|
||||
sectionNames,
|
||||
tables?.map((table) => ({ label: table.name, value: table.collection }))
|
||||
tables?.map((table) => ({ label: table.name, value: table.id }))
|
||||
)}
|
||||
values={{
|
||||
tableType: data?.isCollectionGroup
|
||||
@@ -205,7 +198,7 @@ export default function TableSettingsDialog({
|
||||
<div className={classes.formFooter}>
|
||||
<Confirmation
|
||||
message={{
|
||||
title: `Are you sure you want to delete the table structure for “${formState.name}”?`,
|
||||
title: `Delete the table structure for “${formState.name}”?`,
|
||||
body: (
|
||||
<>
|
||||
<DialogContentText>
|
||||
@@ -241,7 +234,7 @@ export default function TableSettingsDialog({
|
||||
|
||||
<Confirmation
|
||||
message={{
|
||||
title: `Are you sure you want to delete the table “${formState.name}”?`,
|
||||
title: `Delete the table “${formState.name}”?`,
|
||||
body: (
|
||||
<>
|
||||
<DialogContentText>
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ISettingsProps } from "../types";
|
||||
import _sortBy from "lodash/sortBy";
|
||||
|
||||
import { TextField } from "@material-ui/core";
|
||||
import Subheading from "components/Table/ColumnMenu/Subheading";
|
||||
import MultiSelect from "@antlerengineering/multiselect";
|
||||
|
||||
import { FieldType } from "constants/fields";
|
||||
@@ -15,8 +14,8 @@ export default function Settings({ handleChange, config }: ISettingsProps) {
|
||||
const { tables } = useProjectContext();
|
||||
const tableOptions = _sortBy(
|
||||
tables?.map((t) => ({
|
||||
label: `${t.section} - ${t.name}`,
|
||||
value: t.collection,
|
||||
label: `${t.section} – ${t.name} (${t.collection})`,
|
||||
value: t.id,
|
||||
})) ?? [],
|
||||
"label"
|
||||
);
|
||||
@@ -44,16 +43,17 @@ export default function Settings({ handleChange, config }: ISettingsProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Subheading>Table Connect Config</Subheading>
|
||||
<MultiSelect
|
||||
options={tableOptions}
|
||||
freeText={false}
|
||||
value={config.index}
|
||||
onChange={handleChange("index")}
|
||||
multiple={false}
|
||||
label="Table"
|
||||
labelPlural="Tables"
|
||||
/>
|
||||
<TextField
|
||||
label="filter template"
|
||||
label="Filter Template"
|
||||
name="filters"
|
||||
fullWidth
|
||||
value={config.filters}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import queryString from "query-string";
|
||||
import useRouter from "hooks/useRouter";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
export const useSubTableData = (
|
||||
column: any,
|
||||
@@ -18,18 +18,21 @@ export const useSubTableData = (
|
||||
const fieldName = column.key;
|
||||
const documentCount: string = row[fieldName]?.count ?? "";
|
||||
|
||||
const router = useRouter();
|
||||
const parentLabels = queryString.parse(router.location.search).parentLabel;
|
||||
const location = useLocation();
|
||||
const parentLabels = queryString.parse(location.search).parentLabel;
|
||||
const parentPath = decodeURIComponent(
|
||||
location.pathname.split("/").pop() ?? ""
|
||||
);
|
||||
|
||||
let subTablePath = "";
|
||||
if (parentLabels)
|
||||
subTablePath =
|
||||
encodeURIComponent(`${docRef.path}/${fieldName}`) +
|
||||
`?parentLabel=${parentLabels},${label}`;
|
||||
encodeURIComponent(`${parentPath}/${docRef.id}/${fieldName}`) +
|
||||
`?parentLabel=${parentLabels ?? ""},${label ?? ""}`;
|
||||
else
|
||||
subTablePath =
|
||||
encodeURIComponent(`${docRef.path}/${fieldName}`) +
|
||||
`?parentLabel=${encodeURIComponent(label)}`;
|
||||
encodeURIComponent(`${parentPath}/${docRef.id}/${fieldName}`) +
|
||||
`?parentLabel=${encodeURIComponent(label ?? "")}`;
|
||||
|
||||
return { documentCount, label, subTablePath };
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useContext, useEffect, useRef, useMemo } from "react";
|
||||
import { useSnackbar } from "notistack";
|
||||
import _groupBy from "lodash/groupBy";
|
||||
import _sortBy from "lodash/sortBy";
|
||||
import { DataGridHandle } from "react-data-grid";
|
||||
import firebase from "firebase/app";
|
||||
@@ -21,12 +20,12 @@ export type Table = {
|
||||
description: string;
|
||||
section: string;
|
||||
isCollectionGroup: boolean;
|
||||
tableType: string;
|
||||
};
|
||||
|
||||
interface ProjectContextProps {
|
||||
tables: Table[];
|
||||
roles: string[];
|
||||
sections: { [sectionName: string]: Table[] };
|
||||
tableState: TableState;
|
||||
tableActions: TableActions;
|
||||
updateCell: (
|
||||
@@ -41,6 +40,7 @@ interface ProjectContextProps {
|
||||
) => void;
|
||||
settingsActions: {
|
||||
createTable: (data: {
|
||||
id: string;
|
||||
collection: string;
|
||||
name: string;
|
||||
description: string;
|
||||
@@ -48,13 +48,14 @@ interface ProjectContextProps {
|
||||
section: string;
|
||||
}) => void;
|
||||
updateTable: (data: {
|
||||
id: string;
|
||||
collection: string;
|
||||
name: string;
|
||||
description: string;
|
||||
roles: string[];
|
||||
section: string;
|
||||
}) => Promise<any>;
|
||||
deleteTable: (collection: string) => void;
|
||||
deleteTable: (id: string) => void;
|
||||
};
|
||||
|
||||
userClaims: any;
|
||||
@@ -97,7 +98,6 @@ export const ProjectContextProvider: React.FC = ({ children }) => {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const { tableState, tableActions } = useTable();
|
||||
const [tables, setTables] = useState<ProjectContextProps["tables"]>();
|
||||
const [sections, setSections] = useState<ProjectContextProps["sections"]>();
|
||||
const [settings, settingsActions] = useSettings();
|
||||
const [userRoles, setUserRoles] = useState<null | string[]>();
|
||||
const [userClaims, setUserClaims] = useState<any>();
|
||||
@@ -106,7 +106,7 @@ export const ProjectContextProvider: React.FC = ({ children }) => {
|
||||
const [authToken, setAuthToken] = useState("");
|
||||
useEffect(() => {
|
||||
const { tables } = settings;
|
||||
if (tables && userRoles && !sections) {
|
||||
if (tables && userRoles) {
|
||||
const filteredTables = _sortBy(tables, "name")
|
||||
.filter(
|
||||
(table) =>
|
||||
@@ -118,13 +118,14 @@ export const ProjectContextProvider: React.FC = ({ children }) => {
|
||||
section: table.section ? table.section.trim() : "Other",
|
||||
}));
|
||||
|
||||
const _sections = _groupBy(filteredTables, "section");
|
||||
setSections(_sections);
|
||||
setTables(
|
||||
filteredTables.map((table) => ({ ...table, id: table.collection }))
|
||||
filteredTables.map((table) => ({
|
||||
...table,
|
||||
id: table.id || table.collection, // Ensure id exists
|
||||
}))
|
||||
);
|
||||
}
|
||||
}, [settings, userRoles, sections]);
|
||||
}, [settings, userRoles]);
|
||||
|
||||
const roles = useMemo(
|
||||
() =>
|
||||
@@ -228,7 +229,6 @@ export const ProjectContextProvider: React.FC = ({ children }) => {
|
||||
settingsActions,
|
||||
roles,
|
||||
tables,
|
||||
sections,
|
||||
userClaims,
|
||||
dataGridRef,
|
||||
sideDrawerRef,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState } from "react";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
export default function useBasicSearch<T>(
|
||||
collection: T[],
|
||||
list: T[],
|
||||
predicate: (item: T, query: string) => boolean,
|
||||
debounce: number = 400
|
||||
) {
|
||||
@@ -10,8 +10,8 @@ export default function useBasicSearch<T>(
|
||||
const [handleQuery] = useDebouncedCallback(setQuery, debounce);
|
||||
|
||||
const results = query
|
||||
? collection.filter((user) => predicate(user, query.toLowerCase()))
|
||||
: collection;
|
||||
? list.filter((user) => predicate(user, query.toLowerCase()))
|
||||
: list;
|
||||
|
||||
return [results, query, handleQuery] as const;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import _findIndex from "lodash/findIndex";
|
||||
|
||||
import useDoc from "./useDoc";
|
||||
import { db } from "../firebase";
|
||||
import { SETTINGS, TABLE_GROUP_SCHEMAS, TABLE_SCHEMAS } from "config/dbPaths";
|
||||
@@ -21,6 +23,7 @@ const useSettings = () => {
|
||||
}, [settingsState]);
|
||||
|
||||
const createTable = async (data: {
|
||||
id: string;
|
||||
name: string;
|
||||
collection: string;
|
||||
description: string;
|
||||
@@ -34,67 +37,58 @@ const useSettings = () => {
|
||||
tableSettings.tableType !== "collectionGroup"
|
||||
? TABLE_SCHEMAS
|
||||
: TABLE_GROUP_SCHEMAS
|
||||
}/${tableSettings.collection}`;
|
||||
}/${tableSettings.id}`;
|
||||
const tableSchemaDocRef = db.doc(tableSchemaPath);
|
||||
|
||||
let columns = {};
|
||||
// Get columns from schemaSource if provided
|
||||
let columns = [];
|
||||
if (schemaSource) {
|
||||
const schemaSourcePath = `${
|
||||
tableSettings.tableType !== "collectionGroup"
|
||||
? TABLE_SCHEMAS
|
||||
: TABLE_GROUP_SCHEMAS
|
||||
}/${schemaSource.collection}`;
|
||||
}/${schemaSource.id}`;
|
||||
const sourceDoc = await db.doc(schemaSourcePath).get();
|
||||
columns = sourceDoc.get("columns");
|
||||
}
|
||||
// updates the setting doc
|
||||
await db
|
||||
.doc(SETTINGS)
|
||||
.set(
|
||||
{ tables: tables ? [...tables, tableSettings] : [tableSettings] },
|
||||
{ merge: true }
|
||||
);
|
||||
|
||||
//create the rowy collection doc with empty columns
|
||||
await tableSchemaDocRef.set({ ...tableSettings, columns }, { merge: true });
|
||||
// Appends table to settings doc
|
||||
await db.doc(SETTINGS).set(
|
||||
{
|
||||
tables: Array.isArray(tables)
|
||||
? [...tables, tableSettings]
|
||||
: [tableSettings],
|
||||
},
|
||||
{ merge: true }
|
||||
);
|
||||
|
||||
// Creates schema doc with columns
|
||||
await tableSchemaDocRef.set({ columns }, { merge: true });
|
||||
};
|
||||
|
||||
const updateTable = (data: {
|
||||
const updateTable = async (data: {
|
||||
id: string;
|
||||
name: string;
|
||||
collection: string;
|
||||
description: string;
|
||||
roles: string[];
|
||||
}) => {
|
||||
const { tables } = settingsState;
|
||||
const table = tables.filter((t) => t.collection === data.collection)[0];
|
||||
return Promise.all([
|
||||
db.doc(SETTINGS).set(
|
||||
{
|
||||
tables: tables
|
||||
? [
|
||||
...tables.filter(
|
||||
(table) => table.collection !== data.collection
|
||||
),
|
||||
{ table, ...data },
|
||||
]
|
||||
: [data],
|
||||
},
|
||||
{ merge: true }
|
||||
),
|
||||
//update the rowy collection doc with empty columns
|
||||
db
|
||||
.collection(TABLE_SCHEMAS)
|
||||
.doc(data.collection)
|
||||
.set({ ...data }, { merge: true }),
|
||||
]);
|
||||
const newTables = Array.isArray(tables) ? [...tables] : [];
|
||||
const foundIndex = _findIndex(newTables, { id: data.id });
|
||||
const tableIndex = foundIndex > -1 ? foundIndex : tables.length;
|
||||
newTables[tableIndex] = { ...newTables[tableIndex], ...data };
|
||||
|
||||
await db.doc(SETTINGS).set({ tables: newTables }, { merge: true });
|
||||
};
|
||||
const deleteTable = (collection: string) => {
|
||||
|
||||
const deleteTable = (id: string) => {
|
||||
const { tables } = settingsState;
|
||||
|
||||
db.doc(SETTINGS).update({
|
||||
tables: tables.filter((table) => table.collection !== collection),
|
||||
tables: tables.filter((table) => table.id !== id),
|
||||
});
|
||||
db.collection(TABLE_SCHEMAS).doc(collection).delete();
|
||||
db.collection(TABLE_SCHEMAS).doc(id).delete();
|
||||
};
|
||||
const settingsActions = { createTable, updateTable, deleteTable };
|
||||
return [settingsState, settingsActions];
|
||||
|
||||
@@ -13,7 +13,7 @@ export type TableActions = {
|
||||
};
|
||||
row: { add: Function; delete: Function; more: Function; update: Function };
|
||||
table: {
|
||||
set: Function;
|
||||
set: (id: string, collection: string, filters: TableFilter[]) => void;
|
||||
filter: Function;
|
||||
updateConfig: Function;
|
||||
orderBy: Function;
|
||||
@@ -24,6 +24,7 @@ export type TableState = {
|
||||
orderBy: TableOrder;
|
||||
tablePath: string;
|
||||
config: {
|
||||
id: string;
|
||||
rowHeight: number;
|
||||
tableConfig: any;
|
||||
webhooks: any;
|
||||
@@ -45,25 +46,19 @@ export type TableFilter = {
|
||||
};
|
||||
export type TableOrder = { key: string; direction: "asc" | "desc" }[];
|
||||
|
||||
export default function useTable(
|
||||
collectionName?: string,
|
||||
filters?: TableFilter[],
|
||||
orderBy?: TableOrder
|
||||
) {
|
||||
const [tableConfig, configActions] = useTableConfig(collectionName);
|
||||
const [tableState, tableActions] = useTableData({
|
||||
path: collectionName,
|
||||
filters,
|
||||
orderBy,
|
||||
});
|
||||
export default function useTable() {
|
||||
const [tableConfig, configActions] = useTableConfig();
|
||||
const [tableState, tableActions] = useTableData();
|
||||
|
||||
/** set collection path of table */
|
||||
const setTable = (collectionName: string, filters: TableFilter[]) => {
|
||||
if (collectionName !== tableState.path || filters !== tableState.filters) {
|
||||
configActions.setTable(collectionName);
|
||||
tableActions.setTable(collectionName, filters);
|
||||
const setTable = (id: string, collection: string, filters: TableFilter[]) => {
|
||||
if (collection !== tableState.path || filters !== tableState.filters) {
|
||||
configActions.setTable(id);
|
||||
// Wait for config doc to load to get collection path
|
||||
tableActions.setTable(collection, filters);
|
||||
}
|
||||
};
|
||||
|
||||
const filterTable = (filters: TableFilter[]) => {
|
||||
tableActions.dispatch({ filters });
|
||||
};
|
||||
@@ -76,6 +71,7 @@ export default function useTable(
|
||||
filters: tableState.filters,
|
||||
columns: tableConfig.columns,
|
||||
config: {
|
||||
id: tableConfig.id,
|
||||
rowHeight: tableConfig.rowHeight,
|
||||
webhooks: tableConfig.doc?.webhooks,
|
||||
sparks: tableConfig.doc?.sparks,
|
||||
|
||||
@@ -40,12 +40,13 @@ const useTableConfig = (tablePath?: string) => {
|
||||
/** used for specifying the table in use
|
||||
* @param table firestore collection path
|
||||
*/
|
||||
const setTable = (table: string) => {
|
||||
const setTable = (id: string) => {
|
||||
documentDispatch({
|
||||
path: formatPath(table),
|
||||
id,
|
||||
path: formatPath(id),
|
||||
columns: [],
|
||||
doc: null,
|
||||
ref: db.doc(formatPath(table)),
|
||||
ref: db.doc(formatPath(id)),
|
||||
loading: true,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,10 +2,9 @@ import { db } from "../../firebase";
|
||||
import { useSnackbar } from "notistack";
|
||||
|
||||
import Button from "@material-ui/core/Button";
|
||||
import { useEffect, useReducer, useContext } from "react";
|
||||
import { useEffect, useReducer } from "react";
|
||||
import _isEqual from "lodash/isEqual";
|
||||
import _merge from "lodash/merge";
|
||||
import _find from "lodash/find";
|
||||
import firebase from "firebase/app";
|
||||
import { TableFilter, TableOrder } from ".";
|
||||
|
||||
@@ -13,7 +12,6 @@ import {
|
||||
isCollectionGroup,
|
||||
generateSmallerId,
|
||||
missingFieldsReducer,
|
||||
deepMerge,
|
||||
deepen,
|
||||
} from "utils/fns";
|
||||
import { projectId } from "../../firebase";
|
||||
@@ -103,14 +101,14 @@ const tableInitialState = {
|
||||
cap: CAP,
|
||||
};
|
||||
|
||||
const useTableData = (initialOverrides: any) => {
|
||||
const useTableData = () => {
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const { currentUser } = useAppContext();
|
||||
|
||||
const [tableState, tableDispatch] = useReducer(tableReducer, {
|
||||
...tableInitialState,
|
||||
...initialOverrides,
|
||||
});
|
||||
const [tableState, tableDispatch] = useReducer(
|
||||
tableReducer,
|
||||
tableInitialState
|
||||
);
|
||||
const [rows, rowsDispatch] = useReducer(rowsReducer, []);
|
||||
|
||||
/** set collection listener
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
import { useLocation, useRouteMatch } from "react-router-dom";
|
||||
import queryString from "query-string";
|
||||
import _isEmpty from "lodash/isEmpty";
|
||||
import _find from "lodash/find";
|
||||
@@ -16,29 +17,27 @@ import EmptyTable from "components/Table/EmptyTable";
|
||||
import { useProjectContext } from "contexts/ProjectContext";
|
||||
import { useAppContext } from "contexts/AppContext";
|
||||
import { TableFilter } from "hooks/useTable";
|
||||
import useRouter from "hooks/useRouter";
|
||||
import { DocActions } from "hooks/useDoc";
|
||||
import ActionParamsProvider from "components/fields/Action/FormDialog/Provider";
|
||||
|
||||
export default function TablePage() {
|
||||
const router = useRouter();
|
||||
const tableCollection = decodeURIComponent(router.match.params.id);
|
||||
const location = useLocation();
|
||||
const match = useRouteMatch<{ id: string }>();
|
||||
const urlPath = decodeURIComponent(match.params.id);
|
||||
const urlPathSplit = urlPath.split("/");
|
||||
|
||||
const { tableState, tableActions, sideDrawerRef, tables } =
|
||||
useProjectContext();
|
||||
const { userDoc } = useAppContext();
|
||||
|
||||
// Find the matching section for the current route
|
||||
const currentSection = _find(tables, [
|
||||
"collection",
|
||||
tableCollection?.split("/")[0],
|
||||
])?.section;
|
||||
const currentTable = tableCollection?.split("/")[0];
|
||||
const tableName =
|
||||
_find(tables, ["collection", currentTable])?.name || currentTable;
|
||||
const currentTableId = urlPathSplit[0];
|
||||
const currentSection = _find(tables, ["id", currentTableId])?.section;
|
||||
const table = _find(tables, ["id", currentTableId]);
|
||||
const tableName = table?.name || currentTableId;
|
||||
|
||||
let filters: TableFilter[] = [];
|
||||
const parsed = queryString.parse(router.location.search);
|
||||
const parsed = queryString.parse(location.search);
|
||||
if (typeof parsed.filters === "string") {
|
||||
filters = JSON.parse(parsed.filters);
|
||||
// TODO: json schema validator
|
||||
@@ -46,24 +45,26 @@ export default function TablePage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
table &&
|
||||
tableActions &&
|
||||
tableState &&
|
||||
tableState.tablePath !== tableCollection
|
||||
tableState.config.id !== urlPath
|
||||
) {
|
||||
tableActions.table.set(tableCollection, filters);
|
||||
// Support multiple tables for top-level collection but unique sub-table configs
|
||||
const collection = [table.collection, ...urlPathSplit.slice(1)].join("/");
|
||||
|
||||
tableActions.table.set(urlPath, collection, filters);
|
||||
if (filters && filters.length !== 0) {
|
||||
userDoc.dispatch({
|
||||
action: DocActions.update,
|
||||
data: {
|
||||
tables: { [`${tableState.tablePath}`]: { filters } },
|
||||
},
|
||||
data: { tables: { [urlPath]: { filters } } },
|
||||
});
|
||||
}
|
||||
if (sideDrawerRef?.current) sideDrawerRef.current.setCell!(null);
|
||||
}
|
||||
}, [tableCollection]);
|
||||
}, [urlPath, tableActions, tableState, table]);
|
||||
|
||||
if (!tableState) return null;
|
||||
if (!tableState || !table) return null;
|
||||
|
||||
return (
|
||||
<Navigation
|
||||
@@ -81,7 +82,7 @@ export default function TablePage() {
|
||||
<EmptyTable />
|
||||
) : (
|
||||
<>
|
||||
<Table key={tableCollection} />
|
||||
<Table key={currentTableId} />
|
||||
<Hidden smDown>
|
||||
<SideDrawer />
|
||||
</Hidden>
|
||||
|
||||
@@ -94,6 +94,7 @@ export const generateBiggerId = (id: string) => {
|
||||
else return id[0] + generateBiggerId(id.substr(1, id.length - 1));
|
||||
};
|
||||
|
||||
// Gets sub-table ID in $1
|
||||
const formatPathRegex = /\/[^\/]+\/([^\/]+)/g;
|
||||
|
||||
export const formatPath = (tablePath: string) => {
|
||||
|
||||
Reference in New Issue
Block a user