mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
55
ROADMAP.md
55
ROADMAP.md
@@ -4,45 +4,47 @@
|
||||
|
||||
### Initial fields:
|
||||
|
||||
- checkbox(boolean)
|
||||
- simple text(string)
|
||||
- email(string)
|
||||
- phone(string)
|
||||
- url(string)
|
||||
- Number(number)
|
||||
- checkbox(boolean) ✅
|
||||
- simple text(string) ✅
|
||||
- email(string) ✅
|
||||
- phone(string) ✅
|
||||
- url(string) ✅
|
||||
- Number(number) ✅
|
||||
- long text(string)
|
||||
|
||||
### Functionality:
|
||||
|
||||
- Create Tables (Primary collections)
|
||||
- Create columns (fields)
|
||||
- Create rows(documents)
|
||||
- Create Tables (Primary collections) ✅
|
||||
- Create columns (fields) ✅
|
||||
- Create rows(documents) ✅
|
||||
- Edit cells ✅
|
||||
- Authenicate ✅
|
||||
- Delete rows ✅
|
||||
|
||||
## MVP
|
||||
|
||||
### additional fields:
|
||||
|
||||
- tags(array of strings)
|
||||
- single select(string)
|
||||
- [https://material-ui.com/components/chips/#chip-array] Multiple select(array of strings)
|
||||
- date(Firebase timestamp)
|
||||
- time(Firebase timestamp)
|
||||
- index(number)
|
||||
- file(firebase storage url string)
|
||||
- [https://material-ui.com/components/chips/#chip-array]Multiple select(array of strings)
|
||||
- image(firebase storage url string)
|
||||
- reference(DocRefrence)
|
||||
- [https://material-ui.com/components/rating/]rating(number)
|
||||
- single select reference(DocRefrence)
|
||||
- mulit select reference(DocRefrence)
|
||||
- [https://material-ui.com/components/rating/] rating(number)
|
||||
|
||||
### Functionality:
|
||||
|
||||
- Hide/show columns
|
||||
- Delete columns
|
||||
- Delete rows
|
||||
- Edit columns
|
||||
- Delete tables
|
||||
- Filters:
|
||||
- equals to
|
||||
- Starts with
|
||||
- contains
|
||||
- Edit tables
|
||||
- Hide tables
|
||||
- Fixed column
|
||||
- keyboard Navigation: ability to use tab and arrow keys to move focus between cells
|
||||
|
||||
## V1
|
||||
|
||||
@@ -60,3 +62,18 @@
|
||||
- Subcollection tables
|
||||
- Permissions
|
||||
- Duplicate columns
|
||||
- Filters:
|
||||
- equals to
|
||||
- Starts with
|
||||
- contains
|
||||
- Export tables to csv
|
||||
|
||||
# V+
|
||||
|
||||
### Additional Fields:
|
||||
|
||||
- index(number)
|
||||
|
||||
### Functionality:
|
||||
|
||||
- import csv to table
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@date-io/date-fns": "^1.3.11",
|
||||
"@material-ui/core": "^4.4.0",
|
||||
"@material-ui/icons": "^4.4.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.26",
|
||||
"@material-ui/pickers": "^3.2.5",
|
||||
"@types/jest": "24.0.18",
|
||||
"@types/lodash": "^4.14.138",
|
||||
"@types/node": "12.7.4",
|
||||
@@ -16,6 +18,7 @@
|
||||
"@types/react-sortable-hoc": "^0.6.5",
|
||||
"@types/react-virtualized": "^9.21.4",
|
||||
"array-move": "^2.1.0",
|
||||
"date-fns": "^2.0.0-beta.5",
|
||||
"firebase": "^6.6.0",
|
||||
"lodash": "^4.17.15",
|
||||
"ramda": "^0.26.1",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createMuiTheme } from "@material-ui/core";
|
||||
|
||||
import AuthView from "./views/AuthView";
|
||||
import TableView from "./views/TableView";
|
||||
import TablesView from "./views/TablesView";
|
||||
|
||||
import { BrowserRouter as Router, Route } from "react-router-dom";
|
||||
import { AuthProvider } from "./AuthProvider";
|
||||
@@ -26,6 +27,7 @@ const App: React.FC = () => {
|
||||
<CustomBrowserRouter>
|
||||
<div>
|
||||
<Route exact path="/auth" component={AuthView} />
|
||||
<PrivateRoute exact path="/" component={TablesView} />
|
||||
<PrivateRoute path="/table/" component={TableView} />
|
||||
</div>
|
||||
</CustomBrowserRouter>
|
||||
|
||||
@@ -4,6 +4,11 @@ import CheckBoxIcon from "@material-ui/icons/CheckBox";
|
||||
import SimpleTextIcon from "@material-ui/icons/TextFormat";
|
||||
import LongTextIcon from "@material-ui/icons/Notes";
|
||||
import PhoneIcon from "@material-ui/icons/Phone";
|
||||
import DateIcon from "@material-ui/icons/CalendarToday";
|
||||
import TimeIcon from "@material-ui/icons/AccessTime";
|
||||
import RatingIcon from "@material-ui/icons/StarHalf";
|
||||
import URLIcon from "@material-ui/icons/Explore";
|
||||
import NumberIcon from "@material-ui/icons/Looks3";
|
||||
import propEq from "ramda/es/propEq";
|
||||
import find from "ramda/es/find";
|
||||
export enum FieldType {
|
||||
@@ -11,7 +16,13 @@ export enum FieldType {
|
||||
longText = "LONG_TEXT",
|
||||
email = "EMAIL",
|
||||
PhoneNumber = "PHONE_NUMBER",
|
||||
checkBox = "CHECK_BOX"
|
||||
checkBox = "CHECK_BOX",
|
||||
date = "DATE",
|
||||
time = "TIME",
|
||||
dateTime = "DATE_TIME",
|
||||
number = "NUMBER",
|
||||
url = "URL",
|
||||
rating = "RATING"
|
||||
}
|
||||
|
||||
export const FIELDS = [
|
||||
@@ -19,7 +30,12 @@ export const FIELDS = [
|
||||
{ icon: <LongTextIcon />, name: "Long Text", type: FieldType.longText },
|
||||
{ icon: <MailIcon />, name: "Email", type: FieldType.email },
|
||||
{ icon: <PhoneIcon />, name: "Phone", type: FieldType.PhoneNumber },
|
||||
{ icon: <CheckBoxIcon />, name: "Check Box", type: FieldType.checkBox }
|
||||
{ icon: <CheckBoxIcon />, name: "Check Box", type: FieldType.checkBox },
|
||||
{ icon: <NumberIcon />, name: "Number", type: FieldType.number },
|
||||
{ icon: <DateIcon />, name: "Date", type: FieldType.date },
|
||||
{ icon: <TimeIcon />, name: "Time", type: FieldType.time },
|
||||
{ icon: <URLIcon />, name: "URL", type: FieldType.url },
|
||||
{ icon: <RatingIcon />, name: "Rating", type: FieldType.rating }
|
||||
];
|
||||
|
||||
export const getFieldIcon = (type: FieldType) => {
|
||||
|
||||
@@ -51,7 +51,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
})
|
||||
);
|
||||
|
||||
export const Navigation = (props: any) => {
|
||||
const Navigation = (props: any) => {
|
||||
const router = useRouter();
|
||||
const classes = useStyles();
|
||||
const [settings, createTable] = useSettings();
|
||||
@@ -121,3 +121,4 @@ export const Navigation = (props: any) => {
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
export default Navigation;
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
withStyles,
|
||||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import { TableCell as MuiTableCell } from "@material-ui/core";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import {
|
||||
AutoSizer,
|
||||
@@ -17,11 +17,11 @@ import {
|
||||
} from "react-virtualized";
|
||||
import Button from "@material-ui/core/Button";
|
||||
|
||||
// import { TextField } from "@material-ui/core";
|
||||
import { FieldType, getFieldIcon } from "../Fields";
|
||||
import ColumnDrawer from "./ColumnDrawer";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import TableCell from "../components/TableCell";
|
||||
|
||||
import useCell, { Cell } from "../hooks/useCell";
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
flexContainer: {
|
||||
@@ -59,6 +59,8 @@ interface Row {
|
||||
|
||||
interface MuiVirtualizedTableProps extends WithStyles<typeof styles> {
|
||||
columns: ColumnData[];
|
||||
focusedCell: Cell | null;
|
||||
cellActions: any;
|
||||
headerHeight?: number;
|
||||
onRowClick?: () => void;
|
||||
rowCount: number;
|
||||
@@ -91,42 +93,32 @@ class MuiVirtualizedTable extends React.PureComponent<
|
||||
rowData,
|
||||
rowIndex
|
||||
}) => {
|
||||
const { columns, classes, rowHeight, onRowClick } = this.props;
|
||||
const {
|
||||
columns,
|
||||
classes,
|
||||
rowHeight,
|
||||
onRowClick,
|
||||
cellActions,
|
||||
focusedCell
|
||||
} = this.props;
|
||||
const fieldType = columnData.fieldType;
|
||||
if (fieldType === "DELETE")
|
||||
return (
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
columnData.actions.deleteRow(rowIndex, rowData.id);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
component="div"
|
||||
className={clsx(classes.tableCell, classes.flexContainer, {
|
||||
[classes.noClick]: onRowClick == null
|
||||
})}
|
||||
variant="body"
|
||||
onClick={() => {
|
||||
console.log(rowIndex, rowData.id, columnData.fieldName);
|
||||
}}
|
||||
style={{ height: rowHeight }}
|
||||
align={
|
||||
(columnIndex != null && columns[columnIndex].numeric) || false
|
||||
? "right"
|
||||
: "left"
|
||||
}
|
||||
>
|
||||
{cellData} {}
|
||||
</TableCell>
|
||||
fieldType={fieldType}
|
||||
rowIndex={rowIndex}
|
||||
rowData={rowData}
|
||||
columnData={columnData}
|
||||
classes={classes}
|
||||
cellActions={cellActions}
|
||||
cellData={cellData}
|
||||
onRowClick={onRowClick}
|
||||
rowHeight={rowHeight}
|
||||
columnIndex={columnIndex}
|
||||
columns={columns}
|
||||
focusedCell={focusedCell}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
headerRenderer = ({
|
||||
label,
|
||||
columnData,
|
||||
@@ -136,7 +128,7 @@ class MuiVirtualizedTable extends React.PureComponent<
|
||||
const { headerHeight, columns, classes } = this.props;
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
<MuiTableCell
|
||||
component="div"
|
||||
className={clsx(
|
||||
classes.tableCell,
|
||||
@@ -154,7 +146,7 @@ class MuiVirtualizedTable extends React.PureComponent<
|
||||
{getFieldIcon(columnData.fieldType)} {label}
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</MuiTableCell>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -169,34 +161,36 @@ class MuiVirtualizedTable extends React.PureComponent<
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<MuiTable
|
||||
height={height}
|
||||
width={width}
|
||||
rowHeight={rowHeight!}
|
||||
headerHeight={headerHeight!}
|
||||
{...tableProps}
|
||||
rowClassName={this.getRowClassName}
|
||||
>
|
||||
{[
|
||||
...columns.map(({ dataKey, ...other }, index) => {
|
||||
return (
|
||||
<Column
|
||||
key={dataKey}
|
||||
headerRenderer={headerProps =>
|
||||
this.headerRenderer({
|
||||
...headerProps,
|
||||
columnIndex: index
|
||||
})
|
||||
}
|
||||
className={classes.flexContainer}
|
||||
cellRenderer={this.cellRenderer}
|
||||
dataKey={dataKey}
|
||||
{...other}
|
||||
/>
|
||||
);
|
||||
})
|
||||
]}
|
||||
</MuiTable>
|
||||
<>
|
||||
<MuiTable
|
||||
height={height}
|
||||
width={width}
|
||||
rowHeight={rowHeight!}
|
||||
headerHeight={headerHeight!}
|
||||
{...tableProps}
|
||||
rowClassName={this.getRowClassName}
|
||||
>
|
||||
{[
|
||||
...columns.map(({ dataKey, ...other }, index) => {
|
||||
return (
|
||||
<Column
|
||||
key={dataKey}
|
||||
headerRenderer={headerProps =>
|
||||
this.headerRenderer({
|
||||
...headerProps,
|
||||
columnIndex: index
|
||||
})
|
||||
}
|
||||
className={classes.flexContainer}
|
||||
cellRenderer={this.cellRenderer}
|
||||
dataKey={dataKey}
|
||||
{...other}
|
||||
/>
|
||||
);
|
||||
})
|
||||
]}
|
||||
</MuiTable>
|
||||
</>
|
||||
)}
|
||||
</AutoSizer>
|
||||
);
|
||||
@@ -206,12 +200,15 @@ class MuiVirtualizedTable extends React.PureComponent<
|
||||
const VirtualizedTable = withStyles(styles)(MuiVirtualizedTable);
|
||||
|
||||
export default function Table(props: any) {
|
||||
const { columns, rows, addColumn, deleteRow } = props;
|
||||
const [focus, setFocus] = useState(false);
|
||||
const { columns, rows, addColumn, tableActions } = props;
|
||||
|
||||
const [cell, cellActions] = useCell({ updateCell: tableActions.updateCell });
|
||||
if (columns)
|
||||
return (
|
||||
<Paper style={{ height: 400, width: "100%" }}>
|
||||
<VirtualizedTable
|
||||
focusedCell={cell}
|
||||
cellActions={cellActions}
|
||||
rowCount={rows.length}
|
||||
rowGetter={({ index }) => rows[index]}
|
||||
columns={[
|
||||
@@ -237,7 +234,10 @@ export default function Table(props: any) {
|
||||
dataKey: "add",
|
||||
columnData: {
|
||||
fieldType: "DELETE",
|
||||
actions: { addColumn, deleteRow }
|
||||
actions: {
|
||||
addColumn: addColumn,
|
||||
deleteRow: tableActions.deleteRow
|
||||
}
|
||||
}
|
||||
}
|
||||
]}
|
||||
|
||||
157
src/components/TableCell.tsx
Normal file
157
src/components/TableCell.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import React, { useState } from "react";
|
||||
import { TableCell as MuiTableCell, Switch } from "@material-ui/core";
|
||||
import clsx from "clsx";
|
||||
import { FieldType } from "../Fields";
|
||||
import {
|
||||
createStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import Rating from "@material-ui/lab/Rating";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import AddIcon from "@material-ui/icons/AddCircle";
|
||||
import Checkbox, { CheckboxProps } from "@material-ui/core/Checkbox";
|
||||
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import CheckBoxIcon from "@material-ui/icons/CheckBox";
|
||||
import CheckBoxOutlineIcon from "@material-ui/icons/CheckBoxOutlineBlank";
|
||||
import DateFnsUtils from "@date-io/date-fns";
|
||||
import {
|
||||
MuiPickersUtilsProvider,
|
||||
KeyboardTimePicker,
|
||||
KeyboardDatePicker
|
||||
} from "@material-ui/pickers";
|
||||
const TableCell = (props: any) => {
|
||||
const {
|
||||
fieldType,
|
||||
rowIndex,
|
||||
rowData,
|
||||
columnData,
|
||||
classes,
|
||||
cellActions,
|
||||
cellData,
|
||||
onRowClick,
|
||||
rowHeight,
|
||||
columnIndex,
|
||||
columns,
|
||||
focusedCell
|
||||
} = props;
|
||||
const [state, setState] = useState(cellData);
|
||||
const inputRenderer = () => {
|
||||
switch (fieldType) {
|
||||
case FieldType.checkBox:
|
||||
return (
|
||||
<Checkbox
|
||||
checked={state}
|
||||
onChange={e => {
|
||||
setState(!state);
|
||||
cellActions.updateValue(!state);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case FieldType.number:
|
||||
return (
|
||||
<TextField
|
||||
id="number"
|
||||
defaultValue={cellData}
|
||||
onChange={e => {
|
||||
cellActions.updateValue(e.target.value);
|
||||
}}
|
||||
type="number"
|
||||
className={classes.textField}
|
||||
InputLabelProps={{
|
||||
shrink: true
|
||||
}}
|
||||
margin="normal"
|
||||
/>
|
||||
);
|
||||
case FieldType.date:
|
||||
return (
|
||||
<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
||||
<KeyboardDatePicker
|
||||
autoFocus
|
||||
margin="normal"
|
||||
id="date-picker-dialog"
|
||||
label="Date picker dialog"
|
||||
format="MM/dd/yyyy"
|
||||
value={new Date("2014-08-18T21:11:54")}
|
||||
onChange={date => {
|
||||
console.log(date);
|
||||
//cellActions.updateValue(e.target.value);
|
||||
}}
|
||||
KeyboardButtonProps={{
|
||||
"aria-label": "change date"
|
||||
}}
|
||||
/>
|
||||
</MuiPickersUtilsProvider>
|
||||
);
|
||||
|
||||
case FieldType.rating:
|
||||
return <Rating name="pristine" value={null} />;
|
||||
default:
|
||||
return (
|
||||
<TextField
|
||||
autoFocus
|
||||
defaultValue={cellData}
|
||||
onChange={e => {
|
||||
cellActions.updateValue(e.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
const valueRenderer = () => {
|
||||
switch (fieldType) {
|
||||
case FieldType.checkBox:
|
||||
return cellData === true ? <CheckBoxIcon /> : <CheckBoxOutlineIcon />;
|
||||
|
||||
default:
|
||||
return cellData;
|
||||
break;
|
||||
}
|
||||
};
|
||||
if (fieldType === "DELETE")
|
||||
return (
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
columnData.actions.deleteRow(rowIndex, rowData.id);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<MuiTableCell
|
||||
component="div"
|
||||
className={clsx(classes.tableCell, classes.flexContainer, {
|
||||
[classes.noClick]: onRowClick == null
|
||||
})}
|
||||
variant="body"
|
||||
onClick={() => {
|
||||
cellActions.setCell({
|
||||
rowIndex,
|
||||
docId: rowData.id,
|
||||
fieldName: columnData.fieldName,
|
||||
value: cellData
|
||||
});
|
||||
}}
|
||||
style={{ height: rowHeight }}
|
||||
align={
|
||||
(columnIndex != null && columns[columnIndex].numeric) || false
|
||||
? "right"
|
||||
: "left"
|
||||
}
|
||||
>
|
||||
{focusedCell &&
|
||||
focusedCell.fieldName === columnData.fieldName &&
|
||||
focusedCell.rowIndex === rowIndex
|
||||
? inputRenderer()
|
||||
: valueRenderer()}
|
||||
</MuiTableCell>
|
||||
);
|
||||
};
|
||||
export default TableCell;
|
||||
50
src/hooks/useCell.ts
Normal file
50
src/hooks/useCell.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { db } from "../firebase";
|
||||
import { useEffect, useReducer } from "react";
|
||||
import equals from "ramda/es/equals";
|
||||
|
||||
export type Cell = {
|
||||
fieldName: string;
|
||||
rowIndex: number;
|
||||
docId: string;
|
||||
value: any;
|
||||
};
|
||||
|
||||
const cellReducer = (prevState: any, newProps: any) => {
|
||||
return { ...prevState, ...newProps };
|
||||
};
|
||||
const cellIntialState = {
|
||||
prevCell: null,
|
||||
cell: null
|
||||
};
|
||||
|
||||
const useCell = (intialOverrides: any) => {
|
||||
const [cellState, cellDispatch] = useReducer(cellReducer, {
|
||||
...cellIntialState,
|
||||
...intialOverrides
|
||||
});
|
||||
useEffect(() => {
|
||||
const { prevCell, value, updateCell, updatedValue } = cellState;
|
||||
// check for change
|
||||
if (
|
||||
updatedValue !== null &&
|
||||
updatedValue !== undefined &&
|
||||
prevCell &&
|
||||
prevCell.value !== updatedValue
|
||||
) {
|
||||
updateCell({ ...prevCell, value: updatedValue });
|
||||
cellDispatch({ updatedValue: null });
|
||||
}
|
||||
}, [cellState.cell]);
|
||||
const setCell = (cell: Cell) => {
|
||||
cellDispatch({ prevCell: cellState.cell, cell });
|
||||
};
|
||||
const updateValue = (value: any) => {
|
||||
cellDispatch({ updatedValue: value });
|
||||
};
|
||||
|
||||
const actions = { setCell, updateValue };
|
||||
|
||||
return [cellState.cell, actions];
|
||||
};
|
||||
|
||||
export default useCell;
|
||||
21
src/hooks/useFiretable.ts
Normal file
21
src/hooks/useFiretable.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
//TODO: consolidate useTable, useTableConfig, useCell into useFiretable
|
||||
|
||||
import { useEffect } from "react";
|
||||
import useTable from "./useTable";
|
||||
import useTableConfig from "./useTable";
|
||||
import useCell from "./useCell";
|
||||
|
||||
const useFiretable = (collectionName: string) => {
|
||||
const [tableConfig, configActions] = useTableConfig(collectionName);
|
||||
const [table, tableActions] = useTable({
|
||||
path: collectionName
|
||||
});
|
||||
const setTable = (collectionName: string) => {
|
||||
configActions.setTable(collectionName);
|
||||
tableActions.setTable(collectionName);
|
||||
};
|
||||
const actions = { setTable: tableActions.setTable };
|
||||
return [table, actions];
|
||||
};
|
||||
|
||||
export default useFiretable;
|
||||
@@ -1,6 +1,9 @@
|
||||
import { db } from "../firebase";
|
||||
|
||||
import { useEffect, useReducer } from "react";
|
||||
import equals from "ramda/es/equals";
|
||||
import { Cell } from "./useCell";
|
||||
import firebase from "firebase/app";
|
||||
const CAP = 500;
|
||||
|
||||
const tableReducer = (prevState: any, newProps: any) => {
|
||||
@@ -27,6 +30,7 @@ const tableIntialState = {
|
||||
prevLimit: 0,
|
||||
limit: 20,
|
||||
loading: true,
|
||||
sort: { field: "createdAt", direction: "asc" },
|
||||
cap: CAP
|
||||
};
|
||||
|
||||
@@ -131,8 +135,23 @@ const useTable = (intialOverrides: any) => {
|
||||
const setTable = (tableCollection: string) => {
|
||||
tableDispatch({ path: tableCollection });
|
||||
};
|
||||
const updateCell = (cell: Cell) => {
|
||||
console.log("updateCell:", cell);
|
||||
// TODO: update row locally
|
||||
// tableState.rows[cell.rowIndex][cell.fieldName] = cell.value;
|
||||
// tableDispatch({ rows: tableState.rows });
|
||||
|
||||
const tableActions = { deleteRow, setTable };
|
||||
// update document
|
||||
db.collection(tableState.path)
|
||||
.doc(cell.docId)
|
||||
.update({ [cell.fieldName]: cell.value });
|
||||
};
|
||||
const addRow = () => {
|
||||
db.collection(tableState.path).add({
|
||||
createdAt: firebase.firestore.FieldValue.serverTimestamp()
|
||||
});
|
||||
};
|
||||
const tableActions = { deleteRow, setTable, updateCell, addRow };
|
||||
return [tableState, tableActions];
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import Button from "@material-ui/core/Button";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { maxWidth } from "@material-ui/system";
|
||||
import { googleProvider, auth } from "../firebase";
|
||||
import useRouter from "../hooks/useRouter";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
card: {
|
||||
@@ -22,13 +23,15 @@ const useStyles = makeStyles({
|
||||
}
|
||||
});
|
||||
|
||||
googleProvider.addScope("https://www.googleapis.com/auth/contacts.readonly");
|
||||
function handleAuth() {
|
||||
auth.signInWithPopup(googleProvider);
|
||||
}
|
||||
// googleProvider.addScope("https://www.googleapis.com/auth/contacts.readonly");
|
||||
|
||||
export default function AuthView() {
|
||||
const classes = useStyles();
|
||||
|
||||
const router = useRouter();
|
||||
const handleAuth = async () => {
|
||||
await auth.signInWithPopup(googleProvider);
|
||||
router.history.replace("/");
|
||||
};
|
||||
return (
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
|
||||
import { Navigation } from "../components/Navigation";
|
||||
import Navigation from "../components/Navigation";
|
||||
import useTable from "../hooks/useTable";
|
||||
import Table from "../components/Table";
|
||||
import useRouter from "../hooks/useRouter";
|
||||
import useTableConfig from "../hooks/useTableConfig";
|
||||
import Button from "@material-ui/core/Button";
|
||||
const useStyles = makeStyles({});
|
||||
|
||||
export default function AuthView() {
|
||||
@@ -27,8 +28,9 @@ export default function AuthView() {
|
||||
columns={tableConfig.columns}
|
||||
rows={table.rows}
|
||||
addColumn={configActions.addColumn}
|
||||
deleteRow={tableActions.deleteRow}
|
||||
tableActions={tableActions}
|
||||
/>
|
||||
<Button onClick={tableActions.addRow}>Add Row</Button>
|
||||
</Navigation>
|
||||
);
|
||||
}
|
||||
|
||||
67
src/views/TablesView.tsx
Normal file
67
src/views/TablesView.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
import useSettings from "../hooks/useSettings";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardActions from "@material-ui/core/CardActions";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import useRouter from "../hooks/useRouter";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
card: {
|
||||
minWidth: 275
|
||||
},
|
||||
bullet: {
|
||||
display: "inline-block",
|
||||
margin: "0 2px",
|
||||
transform: "scale(0.8)"
|
||||
},
|
||||
title: {
|
||||
fontSize: 14
|
||||
},
|
||||
pos: {
|
||||
marginBottom: 12
|
||||
}
|
||||
});
|
||||
|
||||
const TablesView = (props: any) => {
|
||||
const [settings, createTable] = useSettings();
|
||||
const tables = settings.tables;
|
||||
const classes = useStyles();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
{tables
|
||||
? tables.map((table: any) => (
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
<Typography variant="h5" component="h2">
|
||||
{table.name}
|
||||
</Typography>
|
||||
<Typography className={classes.pos} color="textSecondary">
|
||||
primary
|
||||
</Typography>
|
||||
<Typography variant="body2" component="p">
|
||||
Table summery use
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
router.history.push(`table/${table.collection}`);
|
||||
}}
|
||||
>
|
||||
open{" "}
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
))
|
||||
: "TODO: card skeleton"}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
export default TablesView;
|
||||
47
yarn.lock
47
yarn.lock
@@ -851,7 +851,7 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5":
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.0.tgz#4fc1d642a9fd0299754e8b5de62c631cf5568205"
|
||||
integrity sha512-89eSBLJsxNxOERC0Op4vd+0Bqm6wRMqMbFtV3i0/fbaWw/mJ8Q3eBvgX0G4SyrOOLCtbu98HspF8o09MRT+KzQ==
|
||||
@@ -909,6 +909,18 @@
|
||||
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-9.0.1.tgz#c27b391d8457d1e893f1eddeaf5e5412d12ffbb5"
|
||||
integrity sha512-6It2EVfGskxZCQhuykrfnALg7oVeiI6KclWSmGDqB0AiInVrTGB9Jp9i4/Ad21u9Jde/voVQz6eFX/eSg/UsPA==
|
||||
|
||||
"@date-io/core@^1.3.11":
|
||||
version "1.3.11"
|
||||
resolved "https://registry.yarnpkg.com/@date-io/core/-/core-1.3.11.tgz#98e3c366794dfff571e39227e5d718ac7f26b729"
|
||||
integrity sha512-Yxf2ei0vjU38Fizswr/Uwub5QeRiLOHiTRiHUuTdg+biVB+1EUk+h5szas9SEWA2pZDlSo73F5TPuu+zKqOIBQ==
|
||||
|
||||
"@date-io/date-fns@^1.3.11":
|
||||
version "1.3.11"
|
||||
resolved "https://registry.yarnpkg.com/@date-io/date-fns/-/date-fns-1.3.11.tgz#f0b320b9c5993b9914e3b4d71155ea40814a8d75"
|
||||
integrity sha512-6Pvk4gwCU4L19XYzDUrro861JCQjZkJQjugxAA+M8wsDTW75A5rmSZGa6g2rQQXfg6ox4B7HBx9p6JYDsSPX0g==
|
||||
dependencies:
|
||||
"@date-io/core" "^1.3.11"
|
||||
|
||||
"@emotion/hash@^0.7.1":
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.7.2.tgz#53211e564604beb9befa7a4400ebf8147473eeef"
|
||||
@@ -1307,6 +1319,18 @@
|
||||
prop-types "^15.7.2"
|
||||
warning "^4.0.3"
|
||||
|
||||
"@material-ui/pickers@^3.2.5":
|
||||
version "3.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@material-ui/pickers/-/pickers-3.2.5.tgz#e5c9a56c8b57ceb8bca50c4fd7b7a03cb3c918e1"
|
||||
integrity sha512-UV5UKslOcmcP4cB2wwOg1SFoXS6RTRRvCNkDclHtOa+Ni+gyZLEt3WcSQWH7oDx8A94gmkiZTpfweNFV7sC4sw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.2.0"
|
||||
"@types/styled-jsx" "^2.2.8"
|
||||
clsx "^1.0.2"
|
||||
react-transition-group "^4.0.0"
|
||||
rifm "^0.7.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@material-ui/styles@^4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.4.1.tgz#a53fb39e373636bd2c296a78c54afecb80f68446"
|
||||
@@ -1710,6 +1734,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
|
||||
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
|
||||
|
||||
"@types/styled-jsx@^2.2.8":
|
||||
version "2.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/styled-jsx/-/styled-jsx-2.2.8.tgz#b50d13d8a3c34036282d65194554cf186bab7234"
|
||||
integrity sha512-Yjye9VwMdYeXfS71ihueWRSxrruuXTwKCbzue4+5b2rjnQ//AtyM7myZ1BEhNhBQ/nL/RE7bdToUoLln2miKvg==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "13.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228"
|
||||
@@ -3570,6 +3601,11 @@ data-urls@^1.0.0, data-urls@^1.1.0:
|
||||
whatwg-mimetype "^2.2.0"
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
date-fns@^2.0.0-beta.5:
|
||||
version "2.0.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-beta.5.tgz#90885db3772802d55519cd12acd49de56aca1059"
|
||||
integrity sha512-GS5yi964NDFNoja9yOdWFj9T97T67yLrUeJZgddHaVfc/6tHWtX7RXocuubmZkNzrZUZ9BqBOW7jTR5OoWjJ1w==
|
||||
|
||||
date-now@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||
@@ -9328,6 +9364,13 @@ rgba-regex@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
|
||||
integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
|
||||
|
||||
rifm@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.7.0.tgz#debe951a9c83549ca6b33e5919f716044c2230be"
|
||||
integrity sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.3.1"
|
||||
|
||||
rimraf@2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||
@@ -10279,7 +10322,7 @@ ts-toolbelt@^3.8.4:
|
||||
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-3.8.72.tgz#647ba67a3bd55f1e5a8057c1a7621c38b2e418a5"
|
||||
integrity sha512-rVwnPRczAamCTIs/9iUgWW12YMscmDG79M0xiUmcmWgTk8lkjxrrwzUys72wsIxNohtiNQaOhbkgQPIxqIdwmA==
|
||||
|
||||
tslib@1.10.0, tslib@^1.8.1, tslib@^1.9.0:
|
||||
tslib@1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||
|
||||
Reference in New Issue
Block a user