From e9d4376967a7d89d460cc6e0c860393387e95be7 Mon Sep 17 00:00:00 2001 From: shams mosowi Date: Mon, 11 Nov 2019 11:46:22 +1100 Subject: [PATCH] use snackbar to generator firestore indices --- www/src/components/ExportCSV.tsx | 5 +- www/src/components/Fields/DocSelect.tsx | 4 +- www/src/components/Fields/Image.tsx | 1 - www/src/components/Snack.tsx | 3 +- .../components/Table/ColumnFilter/index.tsx | 190 ++++++++++++++++++ www/src/components/Table/index.tsx | 2 +- .../{snackContext.ts => snackContext.tsx} | 11 +- www/src/hooks/useFiretable/index.ts | 5 +- .../{useTable.ts => useTable.tsx} | 22 +- www/src/util/SnackProvider.tsx | 17 +- 10 files changed, 244 insertions(+), 16 deletions(-) create mode 100644 www/src/components/Table/ColumnFilter/index.tsx rename www/src/contexts/{snackContext.ts => snackContext.tsx} (71%) rename www/src/hooks/useFiretable/{useTable.ts => useTable.tsx} (87%) diff --git a/www/src/components/ExportCSV.tsx b/www/src/components/ExportCSV.tsx index a7573490..3fa6a70e 100644 --- a/www/src/components/ExportCSV.tsx +++ b/www/src/components/ExportCSV.tsx @@ -93,7 +93,10 @@ export default function ExportCSV(props: Props) { } async function handleExport(columns: any[]) { handleClose(); - snackContext.open("preparing file, download will start shortly", 5000); + snackContext.open({ + message: "preparing file, download will start shortly", + duration: 5000, + }); const data = await exportTable({ collectionPath: collection, filters: [], diff --git a/www/src/components/Fields/DocSelect.tsx b/www/src/components/Fields/DocSelect.tsx index e5009b40..9a55f49c 100644 --- a/www/src/components/Fields/DocSelect.tsx +++ b/www/src/components/Fields/DocSelect.tsx @@ -4,6 +4,8 @@ import IconButton from "@material-ui/core/IconButton"; import { createStyles, makeStyles, Theme } from "@material-ui/core/styles"; import Chip from "@material-ui/core/Chip"; +const snapshotReducer = (accumulator: string, currentValue: any) => {}; +const getPrimaryValue = (config: { primaryKeys: string[] }) => {}; const useStyles = makeStyles((theme: Theme) => createStyles({ root: { @@ -39,7 +41,6 @@ const DocSelect = (props: Props) => { const { value, row, onSubmit, collectionPath, config, setSearch } = props; const classes = useStyles(); - const handleClick = ( event: React.MouseEvent ) => { @@ -59,6 +60,7 @@ const DocSelect = (props: Props) => { newValue.splice(index, 1); onSubmit(newValue); }; + return (
diff --git a/www/src/components/Fields/Image.tsx b/www/src/components/Fields/Image.tsx index bdef2538..60365378 100644 --- a/www/src/components/Fields/Image.tsx +++ b/www/src/components/Fields/Image.tsx @@ -59,7 +59,6 @@ const Image = (props: Props) => { const { fieldName, value, row, onSubmit } = props; const [uploaderState, upload] = useUploader(); const { progress } = uploaderState; - console.log(uploaderState); const [localImage, setLocalImage] = useState(null); const onDrop = useCallback(acceptedFiles => { // Do something with the files diff --git a/www/src/components/Snack.tsx b/www/src/components/Snack.tsx index d62e8fd8..7301d44d 100644 --- a/www/src/components/Snack.tsx +++ b/www/src/components/Snack.tsx @@ -4,7 +4,7 @@ import { SnackContext } from "../contexts/snackContext"; export default function Snack() { const snackContext = useContext(SnackContext); - const { position, isOpen, close, message, duration } = snackContext; + const { position, isOpen, close, message, duration, action } = snackContext; const { vertical, horizontal } = position; useEffect(() => { @@ -20,6 +20,7 @@ export default function Snack() { "aria-describedby": "message-id", }} message={{message}} + action={action} /> ); } diff --git a/www/src/components/Table/ColumnFilter/index.tsx b/www/src/components/Table/ColumnFilter/index.tsx new file mode 100644 index 00000000..06c78467 --- /dev/null +++ b/www/src/components/Table/ColumnFilter/index.tsx @@ -0,0 +1,190 @@ +import React, { useEffect, useState } from "react"; +import Button from "@material-ui/core/Button"; +import Typography from "@material-ui/core/Typography"; +import InputLabel from "@material-ui/core/InputLabel"; +import MenuItem from "@material-ui/core/MenuItem"; +import FormHelperText from "@material-ui/core/FormHelperText"; +import FormControl from "@material-ui/core/FormControl"; + +import Popper from "@material-ui/core/Popper"; +import Fade from "@material-ui/core/Fade"; +import Paper from "@material-ui/core/Paper"; +import TextField from "@material-ui/core/TextField"; +import Grid from "@material-ui/core/Grid"; +import Select from "@material-ui/core/Select"; +import ClickAwayListener from "@material-ui/core/ClickAwayListener"; +import { createStyles, makeStyles } from "@material-ui/core/styles"; +import ToggleButton from "@material-ui/lab/ToggleButton"; +import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup"; + +import FormatColorFillIcon from "@material-ui/icons/FormatColorFill"; +import DeleteIcon from "@material-ui/icons/Delete"; + +import { Tooltip } from "@material-ui/core"; + +const useStyles = makeStyles(Theme => + createStyles({ + container: { + padding: 15, + }, + typography: { + padding: 1, + }, + header: { + position: "absolute", + left: 0, + top: 0, + }, + button: { + // margin: theme.spacing(1) + }, + root: { + display: "flex", + flexWrap: "wrap", + }, + formControl: { + margin: Theme.spacing(1), + minWidth: 120, + }, + selectEmpty: { + marginTop: Theme.spacing(2), + }, + toggleGrouped: { + margin: Theme.spacing(0.5), + border: "none", + padding: Theme.spacing(0, 1), + "&:not(:first-child)": { + borderRadius: Theme.shape.borderRadius, + }, + "&:first-child": { + borderRadius: Theme.shape.borderRadius, + }, + }, + }) +); + +const ColumnEditor = (props: any) => { + const { anchorEl, column, handleClose, actions } = props; + + const [values, setValues] = useState({ + type: null, + name: "", + options: [], + collectionPath: "", + config: {}, + }); + const [flags, setFlags] = useState(() => [""]); + const classes = useStyles(); + + function handleChange( + event: React.ChangeEvent<{ name?: string; value: unknown }> + ) { + setValues(oldValues => ({ + ...oldValues, + [event.target.name as string]: event.target.value, + })); + } + const setValue = (key: string, value: any) => { + setValues(oldValues => ({ + ...oldValues, + [key]: value, + })); + }; + + useEffect(() => { + if (column && !column.isNew) { + setValues(oldValues => ({ + ...oldValues, + name: column.name, + type: column.type, + key: column.key, + isNew: column.isNew, + })); + if (column.options) { + setValue("options", column.options); + } else { + setValue("options", []); + } + if (column.collectionPath) { + setValue("collectionPath", column.collectionPath); + } + ["resizable", "editable", "fixed", "hidden"].map(flag => { + if (column[flag]) { + setFlags([...flags, flag]); + } + }); + } + }, [column]); + const clearValues = () => { + setValues({ + type: null, + name: "", + options: [], + collectionPath: "", + config: {}, + }); + }; + const onClickAway = (event: any) => { + const elementId = event.target.id; + if (!elementId.includes("select")) { + handleClose(); + clearValues(); + } + }; + + const handleToggle = ( + event: React.MouseEvent, + newFlags: string[] + ) => { + setFlags(newFlags); + }; + + const createNewColumn = () => { + const { name, type, options, collectionPath, config } = values; + + actions.add(name, type, { options, collectionPath, config }); + + handleClose(); + clearValues(); + }; + const deleteColumn = () => { + actions.remove(props.column.idx); + handleClose(); + clearValues(); + }; + + const disableAdd = () => { + const { type, name, options, collectionPath, config } = values; + if (!type || name === "") return true; + //TODO: Add more validation + return false; + }; + if (column) { + return ( + + + {({ TransitionProps }) => ( + + + + + + )} + + + ); + } + return
; +}; + +export default ColumnEditor; diff --git a/www/src/components/Table/index.tsx b/www/src/components/Table/index.tsx index 47b82a09..0999188a 100644 --- a/www/src/components/Table/index.tsx +++ b/www/src/components/Table/index.tsx @@ -42,7 +42,7 @@ interface Props { function Table(props: Props) { const { collection, filters } = props; - const { tableState, tableActions } = useFiretable(collection); + const { tableState, tableActions } = useFiretable(collection, filters); const [selectedCell, setSelectedCell] = useState<{ row: any; column: any }>({ row: {}, column: {}, diff --git a/www/src/contexts/snackContext.ts b/www/src/contexts/snackContext.tsx similarity index 71% rename from www/src/contexts/snackContext.ts rename to www/src/contexts/snackContext.tsx index 28553728..06cd9309 100644 --- a/www/src/contexts/snackContext.ts +++ b/www/src/contexts/snackContext.tsx @@ -1,5 +1,6 @@ import React from "react"; import { SnackbarOrigin } from "@material-ui/core/Snackbar"; + // Default State of our SnackBar const position: SnackbarOrigin = { vertical: "bottom", horizontal: "left" }; const DEFAULT_STATE = { @@ -8,9 +9,15 @@ const DEFAULT_STATE = { duration: 2000, // time SnackBar should be visible position, close: () => {}, - open: (message: string, duration?: number, position?: SnackbarOrigin) => { - console.log(message, duration); + open: (props: { + message: string; + duration?: number; + position?: SnackbarOrigin; + action?: JSX.Element; + }) => { + console.log(props.message, props.duration); }, + action:
, }; // Create our Context export const SnackContext = React.createContext(DEFAULT_STATE); diff --git a/www/src/hooks/useFiretable/index.ts b/www/src/hooks/useFiretable/index.ts index d218084a..72265469 100644 --- a/www/src/hooks/useFiretable/index.ts +++ b/www/src/hooks/useFiretable/index.ts @@ -33,14 +33,15 @@ export type FireTableFilter = { value: string | number | boolean; }; -const useFiretable = (collectionName: string) => { +const useFiretable = (collectionName: string, filters?: FireTableFilter[]) => { const [tableConfig, configActions] = useTableConfig(collectionName); const [tableState, tableActions] = useTable({ path: collectionName, + filters, }); /** set collection path of table */ const setTable = (collectionName: string, filters: FireTableFilter[]) => { - if (collectionName !== tableState.path) { + if (collectionName !== tableState.path && filters !== tableState.filters) { configActions.setTable(collectionName); tableActions.setTable(collectionName, filters); } diff --git a/www/src/hooks/useFiretable/useTable.ts b/www/src/hooks/useFiretable/useTable.tsx similarity index 87% rename from www/src/hooks/useFiretable/useTable.ts rename to www/src/hooks/useFiretable/useTable.tsx index 987064cd..9d16edfa 100644 --- a/www/src/hooks/useFiretable/useTable.ts +++ b/www/src/hooks/useFiretable/useTable.tsx @@ -1,10 +1,12 @@ import { db } from "../../firebase"; -import { useEffect, useReducer } from "react"; +import Button from "@material-ui/core/Button"; +import React, { useEffect, useReducer, useContext } from "react"; import equals from "ramda/es/equals"; import firebase from "firebase/app"; import { algoliaUpdateDoc } from "../../firebase/callables"; import { FireTableFilter } from "."; +import { SnackContext } from "../../contexts/snackContext"; const CAP = 1000; // safety paramter sets the upper limit of number of docs fetched by this hook @@ -25,6 +27,8 @@ const tableInitialState = { }; const useTable = (initialOverrides: any) => { + const snackContext = useContext(SnackContext); + const [tableState, tableDispatch] = useReducer(tableReducer, { ...tableInitialState, ...initialOverrides, @@ -99,10 +103,24 @@ const useTable = (initialOverrides: any) => { //TODO:callable to create new index if (error.message.includes("indexes?create_composite=")) { const url = - "https://console.firebase.google.com/project/firetable-antler/database/firestore/" + + `https://console.firebase.google.com/project/${process.env.REACT_APP_FIREBASE_PROJECT_NAME}/database/firestore/` + "indexes?create_composite=" + error.message.split("indexes?create_composite=")[1]; console.log(url); + snackContext.open({ + message: "needs a new index", + duration: 10000, + action: ( + + ), + }); } } ); diff --git a/www/src/util/SnackProvider.tsx b/www/src/util/SnackProvider.tsx index 7667ae07..c7cc0e1c 100644 --- a/www/src/util/SnackProvider.tsx +++ b/www/src/util/SnackProvider.tsx @@ -10,6 +10,7 @@ export const SnackProvider: React.FC = ({ children }) => { const [isOpen, setIsOpen] = useState(false); const [message, setMessage] = useState(""); const [duration, setDuration] = useState(3000); + const [action, setAction] = useState(
); const [position, setPosition] = useState({ vertical: "bottom", horizontal: "left", @@ -19,12 +20,17 @@ export const SnackProvider: React.FC = ({ children }) => { setMessage(""); setDuration(0); }; - const open = ( - message: string, - duration?: number, - position?: SnackbarOrigin - ) => { + const open = (props: { + message: string; + duration?: number; + position?: SnackbarOrigin; + action?: JSX.Element; + }) => { + const { message, duration, position, action } = props; setMessage(message); + if (action) { + setAction(action); + } if (duration) { setDuration(duration); } else { @@ -46,6 +52,7 @@ export const SnackProvider: React.FC = ({ children }) => { position, close, open, + action, }} > {children}