diff --git a/package-lock.json b/package-lock.json
index 36466f6..81d08a7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2723,6 +2723,11 @@
"@types/gapi": "*"
}
},
+ "@types/history": {
+ "version": "4.7.2",
+ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.2.tgz",
+ "integrity": "sha512-ui3WwXmjTaY73fOQ3/m3nnajU/Orhi6cEu5rzX+BrAAJxa3eITXZ5ch9suPqtM03OWhAHhPSyBGCN4UKoxO20Q=="
+ },
"@types/istanbul-lib-coverage": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz",
@@ -2775,6 +2780,25 @@
"@types/react": "*"
}
},
+ "@types/react-router": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-4.4.5.tgz",
+ "integrity": "sha512-12+VOu1+xiC8RPc9yrgHCyLI79VswjtuqeS2gPrMcywH6tkc8rGIUhs4LaL3AJPqo5d+RPnfRpNKiJ7MK2Qhcg==",
+ "requires": {
+ "@types/history": "*",
+ "@types/react": "*"
+ }
+ },
+ "@types/react-router-dom": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-4.3.2.tgz",
+ "integrity": "sha512-biesHodFxPgDxku2m08XwPeAfUYBcxAnrQG7pwFikuA3L2e3u2OKAb+Sb16bJuU3L5CTHd+Ivap+ke4mmGsHqQ==",
+ "requires": {
+ "@types/history": "*",
+ "@types/react": "*",
+ "@types/react-router": "*"
+ }
+ },
"@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
diff --git a/package.json b/package.json
index 9c679a4..7e0ad2d 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"@types/node": "^11.13.4",
"@types/react": "^16.8.13",
"@types/react-dom": "^16.8.4",
+ "@types/react-router-dom": "^4.3.2",
"material-components-web": "^1.1.1",
"material-icons": "^0.3.1",
"node-sass": "^4.11.0",
diff --git a/src/App.js b/src/App.js
index 3ead7dc..7168428 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,14 +1,27 @@
import React, { Component } from "react";
-import { Snackbar } from "@material/react-snackbar";
-import { BrowserRouter, Route } from "react-router-dom";
-
-import Settings from "./components/settings/settings";
-import Dashboard from "./components/dashboard/dashboard";
-
import "./App.scss";
-
-class App extends Component {
+import Main from "./components/main";
+import { BrowserRouter } from "react-router-dom";
+
+export default class App extends Component {
+ menu = [
+ {
+ url: '/',
+ icon: "dashboard",
+ text: "Dashboard"
+ },
+ {
+ url: '/charts',
+ icon: "pie_chart",
+ text: "Charts"
+ },
+ {
+ url: '/settings',
+ icon: "settings",
+ text: "Settings"
+ }
+ ];
constructor(props) {
super(props);
@@ -156,22 +169,14 @@ class App extends Component {
render() {
return (
- }
- />
- }
- />
-
-
);
}
}
-
-export default App;
diff --git a/src/components/dashboard/dashboard.js b/src/components/dashboard/dashboard.js
index 506ce30..3377b86 100644
--- a/src/components/dashboard/dashboard.js
+++ b/src/components/dashboard/dashboard.js
@@ -15,9 +15,6 @@ import '@material/react-fab/dist/fab.css';
import { withRouter } from "react-router-dom";
-import TopBar from "../top-bar/top-bar";
-import Hamburger from "../hamburger/hamburger";
-
class Dashboard extends Component {
clientId =
process.env.REACT_APP_GOOGLE_CLIENT_ID ||
@@ -26,27 +23,10 @@ class Dashboard extends Component {
process.env.REACT_APP_SHEET_ID ||
"1eYrQf0xhs2mTSWEzQRfSM-MD-tCcx1r0NVEacLg3Jrc";
- menu = [
- {
- url: '/',
- icon: "dashboard",
- text: "Dashboard"
- },
- {
- url: '/charts',
- icon: "pie_chart",
- text: "Charts"
- },
- {
- url: '/settings',
- icon: "settings",
- text: "Settings"
- }
- ];
-
constructor(props) {
super(props);
this.state = props.state;
+ this.state.expenses = [];
}
componentDidMount() {
@@ -74,10 +54,10 @@ class Dashboard extends Component {
signedInChanged = (signedIn) => {
this.setState({ signedIn });
if (this.state.signedIn) {
- this.setState({ profile: window.gapi.auth2.getAuthInstance().currentUser.get().getBasicProfile() });
+ this.props.signedInChanged(window.gapi.auth2.getAuthInstance().currentUser.get().getBasicProfile());
this.load();
} else {
- this.setState({ profile: null });
+ this.props.signedInChanged(null);
}
};
@@ -125,34 +105,9 @@ class Dashboard extends Component {
};
}
- openDrawer = () => {
- this.setState({ drawerOpen: true });
- };
-
- closeDrawer = () => {
- this.setState({ drawerOpen: false });
- };
-
- navigateTo = (url) => {
- this.props.history.push(url)
- };
-
render() {
return (
-
-
-
+
{this.state.signedIn === undefined && }
{this.state.signedIn === false &&
@@ -169,7 +124,7 @@ class Dashboard extends Component {
}
{this.state.signedIn && this.renderBody()}
-
+
);
}
diff --git a/src/components/hamburger/hamburger.tsx b/src/components/hamburger/hamburger.tsx
index 3b50766..b3ce240 100644
--- a/src/components/hamburger/hamburger.tsx
+++ b/src/components/hamburger/hamburger.tsx
@@ -9,16 +9,16 @@ import "@material/react-drawer/dist/drawer.css";
import List, { ListItem, ListItemGraphic, ListItemText } from "@material/react-list";
import '@material/react-list/dist/list.css';
-type Props = {
+type HamburgerProps = {
drawerOpen?: boolean
closeDrawer: () => void
navigateTo: (url: string) => void
- profile?: gapi.auth2.BasicProfile
+ profile?: gapi.auth2.BasicProfile | null
selectedMenuIndex?: number
menu: Menu
}
-type State = {}
+type HamburgerState = {}
type MenuItem = {
url: string,
@@ -26,9 +26,9 @@ type MenuItem = {
text: string
}
-type Menu = MenuItem[]
+export type Menu = MenuItem[]
-export default class Hamburger extends Component {
+export default class Hamburger extends Component {
private closeDrawer = () => {
if (typeof this.props.closeDrawer === 'function') {
this.props.closeDrawer();
@@ -45,9 +45,11 @@ export default class Hamburger extends Component {
}
};
- render(): React.ReactElement> {
+ render(): React.ReactElement> {
const menu = this.props.menu.map((menuItem: MenuItem) =>
-
+
} />
diff --git a/src/components/main.tsx b/src/components/main.tsx
new file mode 100644
index 0000000..4b897b9
--- /dev/null
+++ b/src/components/main.tsx
@@ -0,0 +1,88 @@
+import React, { Component } from "react";
+
+import { Route, RouteComponentProps, withRouter } from "react-router-dom";
+import TopBar from "./top-bar/top-bar";
+import Hamburger, { Menu } from "./hamburger/hamburger";
+import Dashboard from "./dashboard/dashboard";
+import Settings from "./settings/settings";
+import { Snackbar } from "@material/react-snackbar";
+
+type MainProps = {
+ selectedMenuIndex: number,
+ openDrawer: () => void,
+ closeDrawer: () => void,
+ navigateTo: () => void,
+ menu: Menu,
+}
+
+type MainState = {
+ snackbarMessage: string,
+ drawerOpen: boolean
+ profile: gapi.auth2.BasicProfile | null
+}
+
+class Main extends Component & MainProps, MainState> {
+ state: MainState = {
+ snackbarMessage: '',
+ drawerOpen: false,
+ profile: null,
+ };
+
+ openDrawer = () => {
+ this.setState({ drawerOpen: true });
+ };
+
+ closeDrawer = () => {
+ this.setState({ drawerOpen: false });
+ };
+
+ navigateTo = (url: string) => {
+ this.props.history.push(url);
+ };
+
+ signedInChanged = (profile: gapi.auth2.BasicProfile | null) => {
+ this.setState({ profile });
+ };
+
+ render(): React.ReactElement & MainProps, React.JSXElementConstructor> {
+ return (
+
+
+
+
+
+ }
+ />
+ }
+ />
+
+
+
+ );
+ }
+}
+
+export default withRouter(Main);
diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
index 4cbeddc..f434377 100644
--- a/src/components/settings/settings.js
+++ b/src/components/settings/settings.js
@@ -1,12 +1,171 @@
import React, { Component } from "react";
+import { LoadingBar, OperationForm, OperationsList } from "../index";
+
+import { TopAppBarFixedAdjust } from "@material/react-top-app-bar";
+import '@material/react-top-app-bar/dist/top-app-bar.css';
+
+import MaterialIcon from "@material/react-material-icon";
+import '@material/react-material-icon/dist/material-icon.css';
+
+import { Fab } from "@material/react-fab";
+import '@material/react-fab/dist/fab.css';
+
+import { withRouter } from "react-router-dom";
+
class Settings extends Component {
+ clientId =
+ process.env.REACT_APP_GOOGLE_CLIENT_ID ||
+ "826265862385-p41e559ccssujlfsf49ppmo0gktkf6co.apps.googleusercontent.com";
+ spreadsheetId =
+ process.env.REACT_APP_SHEET_ID ||
+ "1eYrQf0xhs2mTSWEzQRfSM-MD-tCcx1r0NVEacLg3Jrc";
+
+ constructor(props) {
+ super(props);
+ this.state = props.state;
+ }
+
+ componentDidMount() {
+ window.gapi.load("client:auth2", () => {
+ window.gapi.client
+ .init({
+ discoveryDocs: [
+ "https://sheets.googleapis.com/$discovery/rest?version=v4"
+ ],
+ clientId: this.clientId,
+ scope:
+ "https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive.metadata.readonly"
+ })
+ .then(() => {
+ window.gapi.auth2
+ .getAuthInstance()
+ .isSignedIn.listen(this.signedInChanged);
+ this.signedInChanged(
+ window.gapi.auth2.getAuthInstance().isSignedIn.get()
+ );
+ });
+ });
+ }
+
+ signedInChanged = (signedIn) => {
+ this.setState({ signedIn });
+ if (this.state.signedIn) {
+ this.setState({ profile: window.gapi.auth2.getAuthInstance().currentUser.get().getBasicProfile() });
+ this.load();
+ } else {
+ this.setState({ profile: null });
+ }
+ };
+
+ load() {
+ window.gapi.client.sheets.spreadsheets.values
+ .batchGet({
+ spreadsheetId: this.spreadsheetId,
+ ranges: [
+ "Data!A2:A50",
+ "Data!E2:E50",
+ "Expenses!A2:F",
+ "Current!H1",
+ "Previous!H1"
+ ]
+ })
+ .then(response => {
+ const accounts = response.result.valueRanges[0].values.map(
+ items => items[0]
+ );
+ const categories = response.result.valueRanges[1].values.map(
+ items => items[0]
+ );
+ this.setState({
+ accounts: accounts,
+ categories: categories,
+ expenses: (response.result.valueRanges[2].values || [])
+ .map(Settings.parseExpense)
+ .reverse()
+ .slice(0, 30),
+ processing: false,
+ currentMonth: response.result.valueRanges[3].values[0][0],
+ previousMonth: response.result.valueRanges[4].values[0][0]
+ });
+ });
+ }
+
+ static parseExpense(value, index) {
+ return {
+ id: `Expenses!A${index + 2}`,
+ date: value[0],
+ description: value[1],
+ category: value[3],
+ amount: value[4].replace(",", ""),
+ account: value[2]
+ };
+ }
render() {
return (
- test
+
+
+ test
+ {this.state.signedIn === undefined && }
+ {this.state.signedIn === false &&
+
+
+
}
+ {this.state.signedIn && this.renderBody()}
+
+
);
}
+
+ renderBody() {
+ if (this.state.processing) return ;
+ else
+ return (
+
+ {this.renderExpenses()}
+
+ );
+ }
+
+ renderExpenses() {
+ if (this.state.showExpenseForm)
+ return (
+
+ );
+ else { // noinspection RequiredAttributes
+ return (
+
+
+ this.onExpenseNew()}
+ className="add-transaction-fab--fixed"
+ aria-label="Add expense"
+ icon={}
+ />
+
+ );
+ }
+ }
}
-export default Settings;
+export default withRouter(Settings);
diff --git a/src/components/top-bar/top-bar.tsx b/src/components/top-bar/top-bar.tsx
index f13d53d..a475587 100644
--- a/src/components/top-bar/top-bar.tsx
+++ b/src/components/top-bar/top-bar.tsx
@@ -9,14 +9,15 @@ import TopAppBar, {
} from "@material/react-top-app-bar";
import '@material/react-top-app-bar/dist/top-app-bar.css';
-type Props = {
+type TopBarProps = {
title?: string,
- openDrawer: () => void
+ openDrawer: () => void,
+ short?: boolean
}
-type State = {}
+type TopBarState = {}
-export default class TopBar extends Component {
+export default class TopBar extends Component {
private openDrawer = () => {
if (typeof this.props.openDrawer === 'function') {
this.props.openDrawer();
@@ -25,10 +26,10 @@ export default class TopBar extends Component {
}
};
- render(): React.ReactElement> {
+ render(): React.ReactElement> {
// noinspection HtmlDeprecatedAttribute
return