diff --git a/emulators/auth_export/accounts.json b/emulators/auth_export/accounts.json index ab381e8e..b22a33c3 100644 --- a/emulators/auth_export/accounts.json +++ b/emulators/auth_export/accounts.json @@ -1 +1 @@ -{"kind":"identitytoolkit#DownloadAccountResponse","users":[{"localId":"26CJMrwlouNRwkiLofNK07DNgKhw","createdAt":"1651022832613","lastLoginAt":"1651297974462","displayName":"Admin User","photoUrl":"","customAttributes":"{\"roles\": [\"ADMIN\"]}","providerUserInfo":[{"providerId":"google.com","rawId":"abc123","federatedId":"abc123","displayName":"Admin User","email":"admin@example.com"}],"validSince":"1651195467","email":"admin@example.com","emailVerified":true,"disabled":false,"lastRefreshAt":"2022-04-30T08:44:58.158Z"},{"localId":"3xTRVPnJGT2GE6lkiWKZp1jShuXj","createdAt":"1651023059442","lastLoginAt":"1651223181908","displayName":"Editor User","providerUserInfo":[{"providerId":"google.com","rawId":"1535779573397289142795231390488730790451","federatedId":"1535779573397289142795231390488730790451","displayName":"Editor User","email":"editor@example.com"}],"validSince":"1651195467","email":"editor@example.com","emailVerified":true,"disabled":false,"lastRefreshAt":"2022-04-30T08:44:53.855Z"}]} \ No newline at end of file +{"kind":"identitytoolkit#DownloadAccountResponse","users":[{"localId":"26CJMrwlouNRwkiLofNK07DNgKhw","createdAt":"1651022832613","lastLoginAt":"1651630548960","displayName":"Admin User","photoUrl":"","customAttributes":"{\"roles\": [\"ADMIN\"]}","providerUserInfo":[{"providerId":"google.com","rawId":"abc123","federatedId":"abc123","displayName":"Admin User","email":"admin@example.com"}],"validSince":"1651630530","email":"admin@example.com","emailVerified":true,"disabled":false,"lastRefreshAt":"2022-05-04T02:15:48.960Z"},{"localId":"3xTRVPnJGT2GE6lkiWKZp1jShuXj","createdAt":"1651023059442","lastLoginAt":"1651223181908","displayName":"Editor User","providerUserInfo":[{"providerId":"google.com","rawId":"1535779573397289142795231390488730790451","federatedId":"1535779573397289142795231390488730790451","displayName":"Editor User","email":"editor@example.com"}],"validSince":"1651630530","email":"editor@example.com","emailVerified":true,"disabled":false}]} \ No newline at end of file diff --git a/emulators/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata b/emulators/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata index 5f3f7b4d..9e8cd87d 100644 Binary files a/emulators/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata and b/emulators/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata differ diff --git a/emulators/firestore_export/all_namespaces/all_kinds/output-0 b/emulators/firestore_export/all_namespaces/all_kinds/output-0 index 18df228e..78e1cac6 100644 Binary files a/emulators/firestore_export/all_namespaces/all_kinds/output-0 and b/emulators/firestore_export/all_namespaces/all_kinds/output-0 differ diff --git a/src/App.tsx b/src/App.tsx index 869ea8f1..bc68eafd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,6 +31,8 @@ const SetupPage = lazy(() => import("@src/pages/Setup" /* webpackChunkName: "Set // prettier-ignore const TablesPage = lazy(() => import("@src/pages/Tables" /* webpackChunkName: "TablesPage" */)); +// prettier-ignore +const TablePage = lazy(() => import("@src/pages/TableTest" /* webpackChunkName: "TablePage" */)); // prettier-ignore const UserSettingsPage = lazy(() => import("@src/pages/Settings/UserSettings" /* webpackChunkName: "UserSettingsPage" */)); @@ -86,6 +88,11 @@ export default function App() { /> } /> + + } /> + } /> + + } diff --git a/src/atoms/globalScope/project.ts b/src/atoms/globalScope/project.ts index 59f2c438..df8ecd86 100644 --- a/src/atoms/globalScope/project.ts +++ b/src/atoms/globalScope/project.ts @@ -3,8 +3,12 @@ import { sortBy } from "lodash-es"; import { ThemeOptions } from "@mui/material"; import { userRolesAtom } from "./auth"; -import { UpdateDocFunction, UpdateCollectionFunction } from "@src/atoms/types"; import { UserSettings } from "./user"; +import { + UpdateDocFunction, + UpdateCollectionFunction, + TableSettings, +} from "@src/types/table"; export const projectIdAtom = atom(""); @@ -51,23 +55,6 @@ export const projectSettingsAtom = atom({}); export const updateProjectSettingsAtom = atom | null>(null); -/** Table settings stored in project settings */ -export type TableSettings = { - id: string; - collection: string; - name: string; - roles: string[]; - - description: string; - section: string; - - tableType: "primaryCollection" | "collectionGroup"; - - audit?: boolean; - auditFieldCreatedBy?: string; - auditFieldUpdatedBy?: string; - readOnly?: boolean; -}; /** Tables visible to the signed-in user based on roles */ export const tablesAtom = atom((get) => { const userRoles = get(userRolesAtom); diff --git a/src/atoms/globalScope/rowyRun.ts b/src/atoms/globalScope/rowyRun.ts index 4e51305a..e53d102e 100644 --- a/src/atoms/globalScope/rowyRun.ts +++ b/src/atoms/globalScope/rowyRun.ts @@ -72,7 +72,7 @@ export const rowyRunAtom = atom((get) => { handleNotSetUp, }: IRowyRunRequestProps): Promise => { if (!currentUser) { - console.log("Rowy Run: Not signed in"); + console.log("Rowy Run: Not signed in", route.path); if (handleNotSetUp) handleNotSetUp(); return false; } @@ -84,7 +84,7 @@ export const rowyRunAtom = atom((get) => { ? rowyRunServices?.[service] : rowyRunUrl; if (!serviceUrl) { - console.log("Rowy Run: Not set up"); + console.log("Rowy Run: Not set up", route.path); if (handleNotSetUp) handleNotSetUp(); return false; } diff --git a/src/atoms/globalScope/ui.ts b/src/atoms/globalScope/ui.ts index bbfb096c..1725a696 100644 --- a/src/atoms/globalScope/ui.ts +++ b/src/atoms/globalScope/ui.ts @@ -2,7 +2,7 @@ import { atom } from "jotai"; import { atomWithStorage } from "jotai/utils"; import { DialogProps, ButtonProps } from "@mui/material"; -import { TableSettings } from "./project"; +import { TableSettings } from "@src/types/table"; /** Nav open state stored in local storage. */ export const navOpenAtom = atomWithStorage("__ROWY__NAV_OPEN", false); diff --git a/src/atoms/globalScope/user.ts b/src/atoms/globalScope/user.ts index ee3e4367..f4144c27 100644 --- a/src/atoms/globalScope/user.ts +++ b/src/atoms/globalScope/user.ts @@ -5,8 +5,7 @@ import { ThemeOptions } from "@mui/material"; import themes from "@src/theme"; import { publicSettingsAtom } from "./project"; -import { TableFilter } from "@src/atoms/tableScope/table"; -import { UpdateDocFunction } from "@src/atoms/types"; +import { UpdateDocFunction, TableFilter } from "@src/types/table"; /** User info and settings */ export type UserSettings = Partial<{ diff --git a/src/atoms/tableScope/table.ts b/src/atoms/tableScope/table.ts index 995ef4c4..6a5af093 100644 --- a/src/atoms/tableScope/table.ts +++ b/src/atoms/tableScope/table.ts @@ -1,7 +1,18 @@ -import { where } from "firebase/firestore"; +import { atom } from "jotai"; +import { + TableSettings, + TableSchema, + TableFilter, + TableOrder, +} from "@src/types/table"; -export type TableFilter = { - key: Parameters[0]; - operator: Parameters[1]; - value: Parameters[2]; -}; +export const tableIdAtom = atom(undefined); +export const tableSettingsAtom = atom(undefined); +export const tableSchemaAtom = atom(undefined); + +export const tableFiltersAtom = atom([]); +export const tableOrdersAtom = atom([]); +export const tablePageAtom = atom(0); + +export const tableRowsAtom = atom[]>([]); +export const tableLoadingMoreAtom = atom(false); diff --git a/src/atoms/types.d.ts b/src/atoms/types.d.ts deleted file mode 100644 index 04694f3c..00000000 --- a/src/atoms/types.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type UpdateDocFunction = (update: Partial) => Promise; - -export type UpdateCollectionFunction = ( - path: string, - update: Partial -) => Promise; diff --git a/src/components/Table/Skeleton/HeaderRowSkeleton.tsx b/src/components/Table/Skeleton/HeaderRowSkeleton.tsx new file mode 100644 index 00000000..ec416c85 --- /dev/null +++ b/src/components/Table/Skeleton/HeaderRowSkeleton.tsx @@ -0,0 +1,48 @@ +import { Fade, Stack, Skeleton, Button } from "@mui/material"; +import AddColumnIcon from "@src/assets/icons/AddColumn"; + +const NUM_CELLS = 5; + +export default function HeaderRowSkeleton() { + return ( + + + `max(env(safe-area-inset-left), ${theme.spacing(2)})`, + marginRight: `env(safe-area-inset-right)`, + }} + > + {new Array(NUM_CELLS + 1).fill(undefined).map((_, i) => ( + theme.shape.borderRadius : 0, + borderBottomLeftRadius: + i === 0 ? (theme) => theme.shape.borderRadius : 0, + }} + /> + ))} + + + }> + Add column + + + + + ); +} diff --git a/src/components/Table/Skeleton/TableHeaderSkeleton.tsx b/src/components/Table/Skeleton/TableHeaderSkeleton.tsx new file mode 100644 index 00000000..02708be9 --- /dev/null +++ b/src/components/Table/Skeleton/TableHeaderSkeleton.tsx @@ -0,0 +1,60 @@ +import { Fade, Stack, Button, Skeleton, SkeletonProps } from "@mui/material"; +import AddRowIcon from "@src/assets/icons/AddRow"; + +// TODO: +// import { TABLE_HEADER_HEIGHT } from "@src/components/TableHeader"; +const TABLE_HEADER_HEIGHT = 44; + +const ButtonSkeleton = (props: Partial) => ( + +); + +export default function TableHeaderSkeleton() { + return ( + + + + }> + Add row + + + + + + + }> + Hide + + + + }> + Filter + + + + + + + + + + + + ); +} diff --git a/src/components/TableSettingsDialog/DeleteMenu.tsx b/src/components/TableSettingsDialog/DeleteMenu.tsx index d4ccf1b1..1b509fef 100644 --- a/src/components/TableSettingsDialog/DeleteMenu.tsx +++ b/src/components/TableSettingsDialog/DeleteMenu.tsx @@ -6,11 +6,8 @@ import { useSnackbar } from "notistack"; import { IconButton, Menu, MenuItem, DialogContentText } from "@mui/material"; import DeleteIcon from "@mui/icons-material/DeleteOutlined"; -import { - globalScope, - confirmDialogAtom, - TableSettings, -} from "@src/atoms/globalScope"; +import { globalScope, confirmDialogAtom } from "@src/atoms/globalScope"; +import { TableSettings } from "@src/types/table"; import { ROUTES } from "@src/constants/routes"; import { analytics, logEvent } from "@src/analytics"; diff --git a/src/components/TableSettingsDialog/TableSettingsDialog.tsx b/src/components/TableSettingsDialog/TableSettingsDialog.tsx index b924f0c4..b6b59d54 100644 --- a/src/components/TableSettingsDialog/TableSettingsDialog.tsx +++ b/src/components/TableSettingsDialog/TableSettingsDialog.tsx @@ -23,8 +23,8 @@ import { rolesAtom, rowyRunAtom, confirmDialogAtom, - TableSettings, } from "@src/atoms/globalScope"; +import { TableSettings } from "@src/types/table"; import { analytics, logEvent } from "@src/analytics"; // TODO: diff --git a/src/components/Tables/TableGrid/TableCard.tsx b/src/components/Tables/TableGrid/TableCard.tsx index 1bb641ed..0ea7f6e4 100644 --- a/src/components/Tables/TableGrid/TableCard.tsx +++ b/src/components/Tables/TableGrid/TableCard.tsx @@ -11,7 +11,7 @@ import { import GoIcon from "@src/assets/icons/Go"; import RenderedMarkdown from "@src/components/RenderedMarkdown"; -import { TableSettings } from "@src/atoms/globalScope"; +import { TableSettings } from "@src/types/table"; export interface ITableCardProps extends TableSettings { link: string; diff --git a/src/components/Tables/TableGrid/TableGrid.tsx b/src/components/Tables/TableGrid/TableGrid.tsx index cd85d757..20d3b774 100644 --- a/src/components/Tables/TableGrid/TableGrid.tsx +++ b/src/components/Tables/TableGrid/TableGrid.tsx @@ -6,7 +6,7 @@ import SectionHeading from "@src/components/SectionHeading"; import TableCard from "./TableCard"; import SlideTransition from "@src/components/Modal/SlideTransition"; -import { TableSettings } from "@src/atoms/globalScope"; +import { TableSettings } from "@src/types/table"; export interface ITableGridProps { sections: Record; diff --git a/src/components/Tables/TableList/TableList.tsx b/src/components/Tables/TableList/TableList.tsx index 373b4d92..62911137 100644 --- a/src/components/Tables/TableList/TableList.tsx +++ b/src/components/Tables/TableList/TableList.tsx @@ -6,7 +6,7 @@ import SectionHeading from "@src/components/SectionHeading"; import TableListItem from "./TableListItem"; import SlideTransition from "@src/components/Modal/SlideTransition"; -import { TableSettings } from "@src/atoms/globalScope"; +import { TableSettings } from "@src/types/table"; export interface ITableListProps { sections: Record; diff --git a/src/components/Tables/TableList/TableListItem.tsx b/src/components/Tables/TableList/TableListItem.tsx index 24e3943b..e54c56a5 100644 --- a/src/components/Tables/TableList/TableListItem.tsx +++ b/src/components/Tables/TableList/TableListItem.tsx @@ -9,7 +9,7 @@ import { import GoIcon from "@mui/icons-material/ArrowForward"; import RenderedMarkdown from "@src/components/RenderedMarkdown"; -import { TableSettings } from "@src/atoms/globalScope"; +import { TableSettings } from "@src/types/table"; export interface ITableListItemProps extends TableSettings { link: string; diff --git a/src/constants/routes.tsx b/src/constants/routes.tsx index 042dcae4..8c70d88a 100644 --- a/src/constants/routes.tsx +++ b/src/constants/routes.tsx @@ -43,6 +43,8 @@ export const ROUTE_TITLES = { ), }, + [ROUTES.table]: "Table Test", + [ROUTES.settings]: "Settings", [ROUTES.userSettings]: "Settings", [ROUTES.projectSettings]: "Project Settings", diff --git a/src/hooks/useFirestoreCollectionWithAtom.ts b/src/hooks/useFirestoreCollectionWithAtom.ts index a0e3db4e..45d843a4 100644 --- a/src/hooks/useFirestoreCollectionWithAtom.ts +++ b/src/hooks/useFirestoreCollectionWithAtom.ts @@ -17,7 +17,11 @@ import { import { useErrorHandler } from "react-error-boundary"; import { globalScope } from "@src/atoms/globalScope"; -import { UpdateCollectionFunction } from "@src/atoms/types"; +import { + UpdateCollectionFunction, + TableFilter, + TableOrder, +} from "@src/types/table"; import { firebaseDbAtom } from "@src/sources/ProjectSourceFirebase"; /** Options for {@link useFirestoreCollectionWithAtom} */ @@ -25,9 +29,9 @@ interface IUseFirestoreCollectionWithAtomOptions { /** Additional path segments appended to the path. If any are undefined, the listener isn’t created at all. */ pathSegments?: Array; /** Attach filters to the query */ - filters?: Parameters[]; + filters?: TableFilter[]; /** Attach orders to the query */ - orders?: Parameters[]; + orders?: TableOrder[]; /** Called when an error occurs. Make sure to wrap in useCallback! If not provided, errors trigger the nearest ErrorBoundary. */ onError?: (error: FirestoreError) => void; /** Optionally disable Suspense */ @@ -91,8 +95,10 @@ export function useFirestoreCollectionWithAtom( // Create the query with filters and orders const _query = query( collectionRef, - ...(filters?.map((filter) => where(...filter)) || []), - ...(orders?.map((order) => orderBy(...order)) || []) + ...(filters?.map((filter) => + where(filter.key, filter.operator, filter.value) + ) || []), + ...(orders?.map((order) => orderBy(order.key, order.direction)) || []) ); const unsubscribe = onSnapshot( diff --git a/src/hooks/useFirestoreDocWithAtom.ts b/src/hooks/useFirestoreDocWithAtom.ts index 0631e439..ce4b0425 100644 --- a/src/hooks/useFirestoreDocWithAtom.ts +++ b/src/hooks/useFirestoreDocWithAtom.ts @@ -13,7 +13,7 @@ import { import { useErrorHandler } from "react-error-boundary"; import { globalScope } from "@src/atoms/globalScope"; -import { UpdateDocFunction } from "@src/atoms/types"; +import { UpdateDocFunction } from "@src/types/table"; import { firebaseDbAtom } from "@src/sources/ProjectSourceFirebase"; /** Options for {@link useFirestoreDocWithAtom} */ diff --git a/src/layouts/Navigation/NavDrawer.tsx b/src/layouts/Navigation/NavDrawer.tsx index d27c11ea..88ddc78a 100644 --- a/src/layouts/Navigation/NavDrawer.tsx +++ b/src/layouts/Navigation/NavDrawer.tsx @@ -31,15 +31,14 @@ import { userRolesAtom, userSettingsAtom, tablesAtom, - TableSettings, tableSettingsDialogAtom, } from "@src/atoms/globalScope"; +import { TableSettings } from "@src/types/table"; import { ROUTES } from "@src/constants/routes"; export const NAV_DRAWER_WIDTH = 256; export interface INavDrawerProps extends DrawerProps { - currentSection?: string; onClose: NonNullable; pinned: boolean; setPinned: React.Dispatch>; @@ -142,7 +141,7 @@ export default function NavDrawer({ - + diff --git a/src/layouts/Navigation/NavTableSection.tsx b/src/layouts/Navigation/NavTableSection.tsx index 2786d985..cc583f87 100644 --- a/src/layouts/Navigation/NavTableSection.tsx +++ b/src/layouts/Navigation/NavTableSection.tsx @@ -5,32 +5,35 @@ import { List, ListItemText, Collapse } from "@mui/material"; import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; import NavItem from "./NavItem"; -import { TableSettings } from "@src/atoms/globalScope"; +import { TableSettings } from "@src/types/table"; import { ROUTES } from "@src/constants/routes"; -export interface INavDrawerItemProps { - open?: boolean; +const getTableRoute = (table: TableSettings) => + table.tableType === "collectionGroup" + ? `${ROUTES.tableGroup}/${table.id}` + : `${ROUTES.table}/${table.id.replace(/\//g, "~2F")}`; + +export interface INavTableSectionProps { section: string; tables: TableSettings[]; - currentSection?: string; closeDrawer?: (e: {}) => void; } -export default function NavDrawerItem({ - open: openProp, +export default function NavTableSection({ section, tables, - currentSection, closeDrawer, -}: INavDrawerItemProps) { +}: INavTableSectionProps) { const { pathname } = useLocation(); - const [open, setOpen] = useState(openProp || section === currentSection); + const hasMatch = tables.map(getTableRoute).includes(pathname); + + const [open, setOpen] = useState(hasMatch); return ( setOpen((o) => !o)} > @@ -46,31 +49,25 @@ export default function NavDrawerItem({ - {tables - .filter((x) => x) - .map((table) => { - const route = - table.tableType === "collectionGroup" - ? `${ROUTES.tableGroup}/${table.id}` - : `${ROUTES.table}/${table.id.replace(/\//g, "~2F")}`; + {tables.map((table) => { + const route = getTableRoute(table); - return ( - - - `calc(100% - ${theme.spacing(2 + 0.5)})`, - }} - > - - - - ); - })} + return ( + + `calc(100% - ${theme.spacing(2 + 0.5)})`, + }} + > + + + + ); + })} diff --git a/src/layouts/Navigation/Navigation.tsx b/src/layouts/Navigation/Navigation.tsx index ff6bf729..2e648072 100644 --- a/src/layouts/Navigation/Navigation.tsx +++ b/src/layouts/Navigation/Navigation.tsx @@ -49,7 +49,11 @@ export default function Navigation({ children }: React.PropsWithChildren<{}>) { const canPin = !useMediaQuery((theme: any) => theme.breakpoints.down("lg")); const { pathname } = useLocation(); - const routeTitle = ROUTE_TITLES[pathname as keyof typeof ROUTE_TITLES] || ""; + const basePath = ("/" + pathname.split("/")[1]) as keyof typeof ROUTE_TITLES; + const routeTitle = + ROUTE_TITLES[pathname as keyof typeof ROUTE_TITLES] || + ROUTE_TITLES[basePath] || + ""; const title = typeof routeTitle === "string" ? routeTitle : routeTitle.title; useDocumentTitle(projectId, title); diff --git a/src/layouts/Navigation/UserMenu.tsx b/src/layouts/Navigation/UserMenu.tsx index 7cd77859..ef85eb79 100644 --- a/src/layouts/Navigation/UserMenu.tsx +++ b/src/layouts/Navigation/UserMenu.tsx @@ -45,11 +45,7 @@ export default function UserMenu(props: IconButtonProps) { const avatarUrl = userSettings.user?.photoURL; const email = userSettings.user?.email; - const avatar = avatarUrl ? ( - - ) : ( - - ); + const avatar = avatarUrl ? : ; const changeTheme = (option: "system" | "light" | "dark") => { if (option === "system") { diff --git a/src/pages/TableTest.tsx b/src/pages/TableTest.tsx new file mode 100644 index 00000000..d4d0355e --- /dev/null +++ b/src/pages/TableTest.tsx @@ -0,0 +1,49 @@ +import { Suspense } from "react"; +import { useAtom, Provider } from "jotai"; +import { useParams } from "react-router-dom"; + +import { + tableScope, + tableIdAtom, + tableSettingsAtom, + tableSchemaAtom, +} from "@src/atoms/tableScope"; + +import TableSourceFirestore from "@src/sources/TableSourceFirestore"; +import TableHeaderSkeleton from "@src/components/Table/Skeleton/TableHeaderSkeleton"; +import HeaderRowSkeleton from "@src/components/Table/Skeleton/HeaderRowSkeleton"; + +function TableTestPage() { + const [tableId] = useAtom(tableIdAtom, tableScope); + const [tableSettings] = useAtom(tableSettingsAtom, tableScope); + const [tableSchema] = useAtom(tableSchemaAtom, tableScope); + + return ( + + Table ID: {tableId} + + {JSON.stringify(tableSettings, undefined, 2)} + {JSON.stringify(tableSchema, undefined, 2)} + + ); +} + +export default function ProvidedTableTestPage() { + const { id } = useParams(); + + return ( + + + + > + } + > + + + + + + ); +} diff --git a/src/pages/Tables.tsx b/src/pages/Tables.tsx index a16f6e89..588a0b37 100644 --- a/src/pages/Tables.tsx +++ b/src/pages/Tables.tsx @@ -35,8 +35,8 @@ import { tablesAtom, tablesViewAtom, tableSettingsDialogAtom, - TableSettings, } from "@src/atoms/globalScope"; +import { TableSettings } from "@src/types/table"; import { ROUTES } from "@src/constants/routes"; import useBasicSearch from "@src/hooks/useBasicSearch"; import { APP_BAR_HEIGHT } from "@src/layouts/Navigation"; diff --git a/src/sources/ProjectSourceFirebase.tsx b/src/sources/ProjectSourceFirebase.tsx index f3098ab9..caf5c350 100644 --- a/src/sources/ProjectSourceFirebase.tsx +++ b/src/sources/ProjectSourceFirebase.tsx @@ -41,7 +41,7 @@ const envConnectEmulators = /** * Store Firebase config here so it can be set programmatically. * This lets us switch between Firebase projects. - * Then app, auth, db, storage need to be derived atoms. + * Root atom from which app, auth, db, storage are derived. */ export const firebaseConfigAtom = atom(envConfig); diff --git a/src/sources/TableSourceFirestore.tsx b/src/sources/TableSourceFirestore.tsx new file mode 100644 index 00000000..5a85bae8 --- /dev/null +++ b/src/sources/TableSourceFirestore.tsx @@ -0,0 +1,54 @@ +import { memo, useMemo, useEffect } from "react"; +import { useAtom, useSetAtom } from "jotai"; +import { find } from "lodash-es"; + +import { globalScope, tablesAtom } from "@src/atoms/globalScope"; +import { + tableScope, + tableIdAtom, + tableSettingsAtom, + tableSchemaAtom, +} from "@src/atoms/tableScope"; +import { firebaseDbAtom } from "@src/sources/ProjectSourceFirebase"; + +import useFirestoreDocWithAtom from "@src/hooks/useFirestoreDocWithAtom"; + +// import useFirestoreCollectionWithAtom from "@src/hooks/useFirestoreCollectionWithAtom"; +// import { +// globalScope, +// allUsersAtom, +// updateUserAtom, +// } from "@src/atoms/globalScope"; +import { TABLE_SCHEMAS, TABLE_GROUP_SCHEMAS } from "@src/config/dbPaths"; + +const TableSourceFirestore = memo(function TableSourceFirestore() { + const [tables] = useAtom(tablesAtom, globalScope); + const [firebaseDb] = useAtom(firebaseDbAtom, globalScope); + + // Get tableSettings from tableId and tables in globalScope + const [tableId] = useAtom(tableIdAtom, tableScope); + const setTableSettings = useSetAtom(tableSettingsAtom, tableScope); + // Store tableSettings as local const so we don’t re-render + // when tableSettingsAtom is set + const tableSettings = useMemo( + () => find(tables, ["id", tableId]), + [tables, tableId] + ); + // Store in tableSettingsAtom + useEffect(() => { + setTableSettings(tableSettings); + }, [tableSettings, setTableSettings]); + + useFirestoreDocWithAtom( + tableSchemaAtom, + tableScope, + tableSettings?.tableType === "collectionGroup" + ? TABLE_GROUP_SCHEMAS + : TABLE_SCHEMAS, + { pathSegments: [tableId] } + ); + + return null; +}); + +export default TableSourceFirestore; diff --git a/src/types/custom.d.ts b/src/types/files.d.ts similarity index 100% rename from src/types/custom.d.ts rename to src/types/files.d.ts diff --git a/src/types/table.d.ts b/src/types/table.d.ts new file mode 100644 index 00000000..a1e60cf2 --- /dev/null +++ b/src/types/table.d.ts @@ -0,0 +1,61 @@ +import type { where, orderBy } from "firebase/firestore"; + +export type UpdateDocFunction = (update: Partial) => Promise; + +export type UpdateCollectionFunction = ( + path: string, + update: Partial +) => Promise; + +/** Table settings stored in project settings */ +export type TableSettings = { + id: string; + collection: string; + name: string; + roles: string[]; + + description: string; + section: string; + + tableType: "primaryCollection" | "collectionGroup"; + + audit?: boolean; + auditFieldCreatedBy?: string; + auditFieldUpdatedBy?: string; + readOnly?: boolean; +}; + +/** Table schema document loaded when table or table settings dialog is open */ +export type TableSchema = { + columns?: Record; + rowHeight?: number; + filters?: TableFilter[]; + + functionConfigPath?: string; + + extensionObjects?: any[]; + webhooks?: any[]; +}; + +export type ColumnConfig = { + fieldName: string; + key: string; + name: string; + type: FieldType; + index: number; + width?: number; + editable?: boolean; + config: { [key: string]: any }; + [key: string]: any; +}; + +export type TableFilter = { + key: Parameters[0]; + operator: Parameters[1]; + value: Parameters[2]; +}; + +export type TableOrder = { + key: Parameters[0]; + direction: Parameters[1]; +};
Table ID: {tableId}
{JSON.stringify(tableSettings, undefined, 2)}
{JSON.stringify(tableSchema, undefined, 2)}