Merge pull request #10 from rowyio/feat/table-information

Feat: Table Information Drawer
This commit is contained in:
Shams
2022-10-20 18:35:48 +11:00
committed by Sidney Alcantara
parent 7a4bac3673
commit 7fa0955dce
18 changed files with 719 additions and 28 deletions

View File

@@ -103,6 +103,9 @@ export { FileTableBoxOutline as Project };
import { TableColumn } from "mdi-material-ui";
export { TableColumn };
import { InformationOutline } from "mdi-material-ui";
export { InformationOutline as TableInformation };
export * from "./AddRow";
export * from "./AddRowTop";
export * from "./ChevronDown";

View File

@@ -121,6 +121,12 @@ export const importAirtableAtom = atom<{
tableId: string;
}>({ airtableData: null, apiKey: "", baseId: "", tableId: "" });
/** Store side drawer open state */
export const sideDrawerAtom = atomWithHash<"table-information" | null>(
"sideDrawer",
null,
{ replaceState: true }
);
/** Store side drawer open state */
export const sideDrawerOpenAtom = atom(false);

View File

@@ -0,0 +1,178 @@
import { useMemo } from "react";
import { format } from "date-fns";
import MDEditor from "@uiw/react-md-editor";
import {
Box,
Grid,
IconButton,
Stack,
Typography,
useTheme,
} from "@mui/material";
import EditIcon from "@mui/icons-material/EditOutlined";
import { tableScope, tableSettingsAtom } from "@src/atoms/tableScope";
import { useAtom, useSetAtom } from "jotai";
import {
projectScope,
tablesAtom,
tableSettingsDialogAtom,
userRolesAtom,
} from "@src/atoms/projectScope";
import { find } from "lodash-es";
export interface IDetailsProps {
handleOpenTemplate?: any;
}
export default function Details({ handleOpenTemplate }: IDetailsProps) {
const [userRoles] = useAtom(userRolesAtom, projectScope);
const [tableSettings] = useAtom(tableSettingsAtom, tableScope);
const [tables] = useAtom(tablesAtom, projectScope);
const openTableSettingsDialog = useSetAtom(
tableSettingsDialogAtom,
projectScope
);
const settings = useMemo(
() => find(tables, ["id", tableSettings.id]),
[tables, tableSettings.id]
);
const theme = useTheme();
if (!settings) {
return null;
}
const editButton = userRoles.includes("ADMIN") && (
<IconButton
sx={{
position: "absolute",
right: 0,
}}
onClick={() =>
openTableSettingsDialog({
mode: "update",
data: settings,
})
}
disabled={!openTableSettingsDialog || settings.id.includes("/")}
>
<EditIcon />
</IconButton>
);
const { description, details, _createdBy } = settings;
return (
<Grid
container
flexDirection="column"
gap={3}
sx={{
paddingTop: theme.spacing(3),
paddingRight: theme.spacing(3),
paddingBottom: theme.spacing(5),
"& > .MuiGrid-root": {
position: "relative",
},
}}
>
{/* Description */}
<Grid container direction="column" gap={1}>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Typography variant="body1" sx={{ fontWeight: 500 }}>
Description
</Typography>
{editButton}
</Stack>
<Typography variant="body2" color="text.secondary">
{description ? description : "No description"}
</Typography>
</Grid>
{/* Details */}
<Grid container direction="column" gap={1}>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Typography variant="body1" sx={{ fontWeight: 500 }}>
Details
</Typography>
{editButton}
</Stack>
{!details ? (
<Typography variant="body2" color="text.secondary">
No details
</Typography>
) : (
<Box
sx={{
color: "text.secondary",
}}
>
<MDEditor.Markdown source={details} />
</Box>
)}
</Grid>
{/* Table Audits */}
{_createdBy && (
<Grid
container
sx={{
fontSize: theme.typography.body2,
}}
>
<Typography
variant="caption"
color="text.secondary"
component="div"
style={{ whiteSpace: "normal" }}
>
Created by{" "}
<Typography variant="caption" color="text.primary">
{_createdBy.displayName}
</Typography>{" "}
at{" "}
<Typography variant="caption" color="text.primary">
{format(_createdBy.timestamp.toDate(), "LLL d, yyyy · p")}
</Typography>
</Typography>
</Grid>
)}
{/* Template Settings */}
{/* {handleOpenTemplate && (
<Divider sx={{ my: 1.5 }} />
<Button
sx={{ width: "100%", boxShadow: "none", padding: 0 }}
onClick={handleOpenTemplate}
>
<Stack
direction="row"
justify-content="flex-start"
alignItems="center"
sx={{ width: "100%" }}
>
<ChevronLeft />
<ListItemText
primary="Template - Roadmap"
secondary={<StepsProgress steps={5} value={3} />}
sx={{ maxWidth: "188px" }}
/>
</Stack
</Button>
)} */}
</Grid>
);
}

View File

@@ -0,0 +1,134 @@
import { useAtom } from "jotai";
import { RESET } from "jotai/utils";
import { ErrorBoundary } from "react-error-boundary";
import clsx from "clsx";
import {
Box,
Drawer,
drawerClasses,
IconButton,
Stack,
styled,
Typography,
} from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { sideDrawerAtom, tableScope } from "@src/atoms/tableScope";
import { TOP_BAR_HEIGHT } from "@src/layouts/Navigation/TopBar";
import { TABLE_TOOLBAR_HEIGHT } from "@src/components/TableToolbar/TableToolbar";
import ErrorFallback from "@src/components/ErrorFallback";
import Details from "./Details";
export const DRAWER_WIDTH = 450;
export const StyledDrawer = styled(Drawer)(({ theme }) => ({
[`.${drawerClasses.root}`]: {
width: DRAWER_WIDTH,
flexShrink: 0,
whiteSpace: "nowrap",
},
[`.${drawerClasses.paper}`]: {
border: "none",
boxShadow: theme.shadows[4].replace(/, 0 (\d+px)/g, ", -$1 0"),
borderTopLeftRadius: `${(theme.shape.borderRadius as number) * 3}px`,
borderBottomLeftRadius: `${(theme.shape.borderRadius as number) * 3}px`,
width: DRAWER_WIDTH,
maxWidth: `calc(100% - 28px - ${theme.spacing(1)})`,
boxSizing: "content-box",
top: TOP_BAR_HEIGHT + TABLE_TOOLBAR_HEIGHT,
height: `calc(100% - ${TOP_BAR_HEIGHT + TABLE_TOOLBAR_HEIGHT}px)`,
".MuiDialog-paperFullScreen &": {
top:
TOP_BAR_HEIGHT +
TABLE_TOOLBAR_HEIGHT +
Number(theme.spacing(2).replace("px", "")),
height: `calc(100% - ${
TOP_BAR_HEIGHT + TABLE_TOOLBAR_HEIGHT
}px - ${theme.spacing(2)})`,
},
transition: theme.transitions.create("transform", {
easing: theme.transitions.easing.easeInOut,
duration: theme.transitions.duration.standard,
}),
zIndex: theme.zIndex.drawer - 1,
},
[`:not(.sidedrawer-open) .${drawerClasses.paper}`]: {
transform: `translateX(calc(100% - env(safe-area-inset-right)))`,
},
".sidedrawer-contents": {
height: "100%",
overflow: "hidden",
marginLeft: theme.spacing(5),
marginRight: `max(env(safe-area-inset-right), ${theme.spacing(1)})`,
marginTop: theme.spacing(2),
paddingBottom: theme.spacing(5),
},
}));
export default function SideDrawer() {
const [sideDrawer, setSideDrawer] = useAtom(sideDrawerAtom, tableScope);
// const DetailsComponent =
// userRoles.includes("ADMIN") && tableSettings.templateSettings
// ? withTemplate(Details)
// : Details;
// const DetailsComponent = Details;
const open = sideDrawer === "table-information";
return (
<StyledDrawer
className={clsx(open && "sidedrawer-open")}
open={open}
variant="permanent"
anchor="right"
PaperProps={{ elevation: 4, component: "aside" } as any}
>
<ErrorBoundary FallbackComponent={ErrorFallback}>
{open && (
<div className="sidedrawer-contents">
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
pr={3}
>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
py={1}
>
<Typography variant="h5" sx={{ fontWeight: "bold" }}>
Information
</Typography>
</Stack>
<IconButton
onClick={() => setSideDrawer(RESET)}
aria-label="Close"
>
<CloseIcon />
</IconButton>
</Stack>
<Box
sx={{
height: "100%",
overflow: "auto",
}}
>
<Details />
</Box>
</div>
)}
</ErrorBoundary>
</StyledDrawer>
);
}

View File

@@ -0,0 +1,2 @@
export * from "../TableToolbar/TableInformation";
export { default } from "../TableToolbar/TableInformation";

View File

@@ -0,0 +1,61 @@
// import {
// projectScope,
// tablesAtom,
// templateSettingsDialogAtom,
// } from "@src/atoms/projectScope";
// import { TemplateSettings } from "@src/components/Tables/Templates";
// import { getTemplateById } from "@src/components/Tables/Templates/utills";
// import { useAtom, useSetAtom } from "jotai";
// import { useEffect, useState } from "react";
import Details from "./Details";
export default function withTemplate(DetailsComponent: typeof Details) {
// const [tables] = useAtom(tablesAtom, projectScope);
// const setTemplateSettingsDialog = useSetAtom(
// templateSettingsDialogAtom,
// projectScope
// );
// const [templateData, setTemplateData] = useState<TemplateSettings | null>(
// null
// );
// const { templateSettings } = tableSettings;
// const { draftId, templateId } = templateSettings || {};
// useEffect(() => {
// if (!templateId) {
// throw Error("Template not found");
// }
// getTemplateById(templateId).then((template) => setTemplateData(template));
// }, [templateId]);
// const dialogStateInitializer = () => {
// const matchingTables =
// tables.filter((table) => table.templateSettings?.draftId === draftId) ||
// [];
// const templateTables = templateData!.tables.map((templateTable) => ({
// ...templateTable,
// ...matchingTables.find(
// (matchingTable) =>
// matchingTable.templateSettings?.tableId === templateTable.id
// ),
// }));
// const steps = templateData?.steps.map((step) =>
// step.type === "create_table" ? { ...step, completed: true } : step
// );
// return {
// ...templateData,
// tables: templateTables,
// steps,
// } as TemplateSettings;
// };
// const handleOpenTemplate = () => {
// setTemplateSettingsDialog({
// type: "set_open",
// data: dialogStateInitializer(),
// });
// };
// if (!templateData) {
// return null;
// }
return <DetailsComponent handleOpenTemplate={() => {}} />;
}
// export const DetailsWithTemplate = withTemplate(Details);

View File

@@ -0,0 +1,43 @@
import { Box, InputLabel, useTheme } from "@mui/material";
import MDEditor from "@uiw/react-md-editor";
import { useState } from "react";
export default function TableDetails({ ...props }) {
const {
field: { value, onChange },
} = props;
const theme = useTheme();
const [focused, setFocused] = useState(false);
return (
<>
<InputLabel htmlFor="table-details__md-text-area" focused={focused}>
{props.label ?? ""}
</InputLabel>
<Box
sx={{
"& .w-md-editor": {
backgroundColor: `${theme.palette.action.input} !important`,
},
"& .w-md-editor-fullscreen": {
backgroundColor: `${theme.palette.background.paper} !important`,
},
}}
>
<MDEditor
style={{ margin: 1 }}
preview="live"
toolbarHeight={52}
height={150}
value={value}
onChange={onChange}
textareaProps={{
id: "table-details__md-text-area",
onFocus: () => setFocused(true),
onBlur: () => setFocused(false),
}}
{...props}
/>
</Box>
</>
);
}

View File

@@ -38,6 +38,10 @@ import {
getTableSchemaPath,
getTableBuildFunctionPathname,
} from "@src/utils/table";
import { firebaseStorageAtom } from "@src/sources/ProjectSourceFirebase";
import { uploadTableThumbnail } from "./utils";
import TableThumbnail from "./TableThumbnail";
import TableDetails from "./TableDetails";
const customComponents = {
tableName: {
@@ -55,6 +59,12 @@ const customComponents = {
defaultValue: "",
validation: [["string"]],
},
tableThumbnail: {
component: TableThumbnail,
},
tableDetails: {
component: TableDetails,
},
};
export default function TableSettingsDialog() {
@@ -67,6 +77,7 @@ export default function TableSettingsDialog() {
const [projectRoles] = useAtom(projectRolesAtom, projectScope);
const [tables] = useAtom(tablesAtom, projectScope);
const [rowyRun] = useAtom(rowyRunAtom, projectScope);
const [firebaseStorage] = useAtom(firebaseStorageAtom, projectScope);
const navigate = useNavigate();
const confirm = useSetAtom(confirmDialogAtom, projectScope);
@@ -96,15 +107,26 @@ export default function TableSettingsDialog() {
if (!open) return null;
const handleSubmit = async (v: TableSettings & AdditionalTableSettings) => {
const handleSubmit = async (
v: TableSettings & AdditionalTableSettings & { thumbnailFile: File }
) => {
const {
_schemaSource,
_initialColumns,
_schema,
_suggestedRules,
thumbnailFile,
...values
} = v;
const data = { ...values };
let thumbnailURL = values.thumbnailURL;
if (thumbnailFile) {
thumbnailURL = await uploadTableThumbnail(firebaseStorage)(
values.id,
thumbnailFile
);
}
const data = { ...values, thumbnailURL };
const hasExtensions = !isEmpty(get(data, "_schema.extensionObjects"));
const hasWebhooks = !isEmpty(get(data, "_schema.webhooks"));

View File

@@ -0,0 +1,126 @@
import { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
import { IFieldComponentProps } from "@rowy/form-builder";
import { Button, Grid, IconButton, InputLabel, useTheme } from "@mui/material";
import { Upload as UploadImageIcon } from "@src/assets/icons";
import {
OpenInFull as ExpandIcon,
CloseFullscreen as CollapseIcon,
AddPhotoAlternateOutlined as NoImageIcon,
} from "@mui/icons-material";
import { IMAGE_MIME_TYPES } from "@src/components/fields/Image";
export default function TableThumbnail({ ...props }: IFieldComponentProps) {
const {
name,
useFormMethods: { setValue, getValues },
} = props;
const theme = useTheme();
const [localImage, setLocalImage] = useState<string | undefined>(
() => getValues().thumbnailURL
);
const [expanded, setExpanded] = useState(false);
const onDrop = useCallback(
(acceptedFiles: File[]) => {
const imageFile = acceptedFiles[0];
if (imageFile) {
setLocalImage(URL.createObjectURL(imageFile));
setValue(name, imageFile);
}
},
[name, setLocalImage, setValue]
);
const { getInputProps } = useDropzone({
onDrop,
multiple: false,
accept: IMAGE_MIME_TYPES,
});
return (
<Grid container>
<Grid
container
alignItems="center"
xs={expanded ? 12 : 10.5}
sx={{
marginRight: "auto",
transition: "all 0.1s",
}}
>
<InputLabel htmlFor="thumbnail-image__input">{props.label}</InputLabel>
<IconButton
component="label"
sx={{
marginLeft: "auto",
marginRight: expanded ? 0 : theme.spacing(0.5),
}}
>
<UploadImageIcon />
<input
id="thumbnail-image__input"
type="file"
hidden
{...getInputProps()}
/>
</IconButton>
</Grid>
<Grid
item
xs={expanded ? 12 : 1.5}
sx={{
marginLeft: "auto",
marginTop: expanded ? theme.spacing(1) : 0,
transition: "all 0.5s",
}}
>
<Grid
container
sx={{
position: "relative",
// 16:9 ratio
paddingBottom: "56.25%",
}}
>
<Button
disabled={!localImage}
sx={{
position: "absolute",
width: "100%",
height: "100%",
backgroundImage: `url("${localImage}")`,
backgroundSize: "cover",
backgroundPosition: "center center",
backgroundRepeat: "no-repeat",
"& > svg": {
display: localImage ? "none" : "block",
},
"&:hover": {
opacity: 0.75,
},
"&:hover > svg": {
display: "block",
},
}}
onClick={() => setExpanded(!expanded)}
>
{!localImage ? (
<NoImageIcon />
) : expanded ? (
<CollapseIcon />
) : (
<ExpandIcon />
)}
</Button>
</Grid>
</Grid>
</Grid>
);
}

View File

@@ -244,7 +244,23 @@ export const tableSettings = (
type: FieldType.paragraph,
name: "description",
label: "Description (optional)",
minRows: 2,
},
{
step: "display",
type: "tableDetails",
name: "details",
label: "Details (optional)",
},
{
step: "display",
type: "tableThumbnail",
name: "thumbnailFile",
label: "Thumbnail Image (optional)",
},
{
step: "display",
type: FieldType.hidden,
name: "thumbnailURL",
},
// Step 3: Access controls

View File

@@ -0,0 +1,14 @@
import {
FirebaseStorage,
getDownloadURL,
ref,
uploadBytes,
} from "firebase/storage";
export const uploadTableThumbnail =
(storage: FirebaseStorage) => (tableId: string, imageFile: File) => {
const storageRef = ref(storage, `__thumbnails__/${tableId}`);
return uploadBytes(storageRef, imageFile).then(({ ref }) =>
getDownloadURL(ref)
);
};

View File

@@ -0,0 +1,25 @@
import { useAtom } from "jotai";
import { RESET } from "jotai/utils";
import {
sideDrawerAtom,
tableScope,
tableSettingsAtom,
} from "@src/atoms/tableScope";
import TableToolbarButton from "@src/components/TableToolbar/TableToolbarButton";
import { TableInformation as TableInformationIcon } from "@src/assets/icons";
export default function TableInformation() {
const [tableSettings] = useAtom(tableSettingsAtom, tableScope);
const [sideDrawer, setSideDrawer] = useAtom(sideDrawerAtom, tableScope);
return (
<TableToolbarButton
title="Table Information"
icon={<TableInformationIcon />}
onClick={() => setSideDrawer(sideDrawer ? RESET : "table-information")}
disabled={!setSideDrawer || tableSettings.id.includes("/")}
/>
);
}

View File

@@ -36,6 +36,9 @@ import { FieldType } from "@src/constants/fields";
const Filters = lazy(() => import("./Filters" /* webpackChunkName: "Filters" */));
// prettier-ignore
const ImportData = lazy(() => import("./ImportData/ImportData" /* webpackChunkName: "ImportData" */));
// prettier-ignore
const TableInformation = lazy(() => import("./TableInformation" /* webpackChunkName: "TableInformation" */));
// prettier-ignore
const ReExecute = lazy(() => import("./ReExecute" /* webpackChunkName: "ReExecute" */));
@@ -147,6 +150,9 @@ export default function TableToolbar() {
<TableSettings />
</>
)}
<Suspense fallback={<ButtonSkeleton />}>
<TableInformation />
</Suspense>
<div className="end-spacer" />
</Stack>
);

View File

@@ -7,10 +7,9 @@ import {
Typography,
CardActions,
Button,
Box,
} from "@mui/material";
import { Go as GoIcon } from "@src/assets/icons";
import RenderedMarkdown from "@src/components/RenderedMarkdown";
import { TableSettings } from "@src/types/table";
export interface ITableCardProps extends TableSettings {
@@ -19,6 +18,7 @@ export interface ITableCardProps extends TableSettings {
}
export default function TableCard({
thumbnailURL,
section,
name,
description,
@@ -37,27 +37,46 @@ export default function TableCard({
</Typography>
</CardContent>
</CardActionArea>
<CardContent style={{ flexGrow: 1, paddingTop: 0 }}>
<Typography
color="textSecondary"
sx={{
minHeight: (theme) =>
(theme.typography.body2.lineHeight as number) * 2 + "em",
display: "flex",
flexDirection: "column",
gap: 1,
}}
component="div"
>
{description && (
<RenderedMarkdown
children={description}
//restrictionPreset="singleLine"
{thumbnailURL && (
<CardContent style={{ flexGrow: 1, paddingTop: 0 }}>
<Box
sx={{
paddingBottom: "56.25%",
position: "relative",
backgroundColor: "action.input",
borderRadius: 1,
overflow: "hidden",
}}
>
<Box
sx={{
position: "absolute",
width: "100%",
height: "100%",
backgroundImage: `url("${thumbnailURL}")`,
backgroundSize: "cover",
backgroundPosition: "center center",
backgroundRepeat: "no-repeat",
}}
/>
)}
</Typography>
</CardContent>
</Box>
</CardContent>
)}
{description && (
<CardContent style={{ flexGrow: 1, paddingTop: 0, paddingBottom: 0 }}>
<Typography
color="textSecondary"
sx={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
}}
component="div"
>
{description}
</Typography>
</CardContent>
)}
<CardActions>
<Button

View File

@@ -8,6 +8,7 @@ import { Fade } from "@mui/material";
import ErrorFallback, {
InlineErrorFallback,
} from "@src/components/ErrorFallback";
import TableInformationDrawer from "@src/components/TableInformationDrawer/SideDrawer";
import TableToolbarSkeleton from "@src/components/TableToolbar/TableToolbarSkeleton";
import TableSkeleton from "@src/components/Table/TableSkeleton";
import EmptyTable from "@src/components/Table/EmptyTable";
@@ -103,6 +104,12 @@ export default function TablePage({
</Suspense>
</ErrorBoundary>
<ErrorBoundary FallbackComponent={InlineErrorFallback}>
<Suspense fallback={null}>
<TableInformationDrawer />
</Suspense>
</ErrorBoundary>
{!disableModals && (
<ErrorBoundary FallbackComponent={InlineErrorFallback}>
<Suspense fallback={null}>

View File

@@ -1,5 +1,6 @@
import { useAtom, useSetAtom } from "jotai";
import { find, groupBy, sortBy } from "lodash-es";
import { useNavigate } from "react-router-dom";
import {
Container,
@@ -13,12 +14,14 @@ import {
IconButton,
Zoom,
} from "@mui/material";
import ViewListIcon from "@mui/icons-material/ViewListOutlined";
import ViewGridIcon from "@mui/icons-material/ViewModuleOutlined";
import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";
import FavoriteIcon from "@mui/icons-material/Favorite";
import EditIcon from "@mui/icons-material/EditOutlined";
import AddIcon from "@mui/icons-material/Add";
import { TableInformation as TableInformationIcon } from "@src/assets/icons";
import FloatingSearch from "@src/components/FloatingSearch";
import SlideTransition from "@src/components/Modal/SlideTransition";
@@ -54,7 +57,7 @@ export default function TablesPage() {
tableSettingsDialogAtom,
projectScope
);
const navigate = useNavigate();
useScrollToHash();
const [results, query, handleQuery] = useBasicSearch(
@@ -159,6 +162,16 @@ export default function TablesPage() {
sx={view === "list" ? { p: 1.5 } : undefined}
color="secondary"
/>
<IconButton
aria-label="Table Information"
size={view === "list" ? "large" : undefined}
onClick={() => {
navigate(`${getLink(table)}#sideDrawer="table-information"`);
}}
style={{ marginLeft: 0 }}
>
<TableInformationIcon />
</IconButton>
</>
);

View File

@@ -13,6 +13,7 @@ import {
getTableSchemaAtom,
AdditionalTableSettings,
MinimumTableSettings,
currentUserAtom,
} from "@src/atoms/projectScope";
import { firebaseDbAtom } from "./init";
@@ -21,12 +22,14 @@ import {
TABLE_SCHEMAS,
TABLE_GROUP_SCHEMAS,
} from "@src/config/dbPaths";
import { rowyUser } from "@src/utils/table";
import { TableSettings, TableSchema } from "@src/types/table";
import { FieldType } from "@src/constants/fields";
import { getFieldProp } from "@src/components/fields";
export function useTableFunctions() {
const [firebaseDb] = useAtom(firebaseDbAtom, projectScope);
const [currentUser] = useAtom(currentUserAtom, projectScope);
// Create a function to get the latest tables from project settings,
// so we dont create new functions when tables change
@@ -93,10 +96,11 @@ export function useTableFunctions() {
};
}
const _createdBy = currentUser && rowyUser(currentUser);
// Appends table to settings doc
const promiseUpdateSettings = setDoc(
doc(firebaseDb, SETTINGS),
{ tables: [...tables, settings] },
{ tables: [...tables, { ...settings, _createdBy }] },
{ merge: true }
);
@@ -120,7 +124,7 @@ export function useTableFunctions() {
await Promise.all([promiseUpdateSettings, promiseAddSchema]);
}
);
}, [firebaseDb, readTables, setCreateTable]);
}, [currentUser, firebaseDb, readTables, setCreateTable]);
// Set the createTable function
const setUpdateTable = useSetAtom(updateTableAtom, projectScope);

12
src/types/table.d.ts vendored
View File

@@ -70,6 +70,18 @@ export type TableSettings = {
section: string;
description?: string;
details?: string;
thumbnailURL?: string;
_createdBy?: {
displayName?: string;
email?: string;
emailVerified: boolean;
isAnonymous: boolean;
photoURL?: string;
uid: string;
timestamp: firebase.firestore.Timestamp;
};
tableType: "primaryCollection" | "collectionGroup";