diff --git a/README.md b/README.md index 1f008c30..035fc80b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,26 @@ [ ![Codeship Status for databraid-dashboard/sheet-spa](https://app.codeship.com/projects/7bcb20f0-83d6-0135-9473-62a24314a0c3/status?branch=master)](https://app.codeship.com/projects/247369) # Sheet SPA + +The Sheet widget is to allow a Google Sheets users to view their sheets. +When first using the widget, the user interface displays a login screen; following the initial login/authentication step, the users are given a form to fill out to specify which spreadsheet they wish to view and the specific sheet's name. Once viewing a sheet, the user can also pick from a list the other sheets in that spreadsheet. Users can also return to the form to load a different sheet. + + +Features +---------- +- Access to Google Sheets in the dashboard slack-spa widget +- Ability to select a sheet in each spreadsheet. + +Installation +-------------- +Install sheet-spa by running: + `npm install @databraid/sheets-widget` + +Contribute +------------ +- Issue Tracker: github.com/databraid-dashboard/issues +- Source Code: github.com/databraid-dashboard/ + +License +--------- +The project is licensed under the MIT license. diff --git a/libs/widget-libs/package.json b/libs/widget-libs/package.json index a6f8ae6a..c7b73cfb 100644 --- a/libs/widget-libs/package.json +++ b/libs/widget-libs/package.json @@ -1,6 +1,6 @@ { "name": "@databraid/sheets-widget", - "version": "1.0.11", + "version": "1.0.12", "description": "", "main": "index.js", "files": [ diff --git a/src/actions/index.js b/src/actions/index.js index 8ac8045e..a13935dd 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -1,6 +1,7 @@ import { RENDER_SHEETS } from './renderActions'; export const SHEET_RETRIEVED = 'SHEET_RETRIEVED'; +export const LOAD_SHEETLIST_DATA = 'LOAD_SHEETLIST_DATA'; export const STORE_SHEET_DATA = 'STORE_SHEET_DATA'; export function fetchSheet(spreadsheetId, sheetName) { @@ -31,6 +32,26 @@ export function fetchSheet(spreadsheetId, sheetName) { }); }; } +export function loadSheetList(spreadsheetId) { + const queryString = + `{ + spreadsheet(spreadsheetId: "${spreadsheetId}") { + sheets + } +} +`; + const request = { query: queryString }; + return (dispatch, getState, { SHEETS_API }) => { + SHEETS_API.fetchData(request) + .then(response => response.data) + .then((spreadSheet) => { + dispatch({ + type: LOAD_SHEETLIST_DATA, + sheetList: spreadSheet.spreadsheet.sheets, + }); + }); + }; +} export function storeSheetData(spreadsheetId, sheetName) { return (dispatch) => { @@ -40,5 +61,6 @@ export function storeSheetData(spreadsheetId, sheetName) { sheetName, }); dispatch(fetchSheet(spreadsheetId, sheetName)); + dispatch(loadSheetList(spreadsheetId)); }; } diff --git a/src/actions/renderActions.js b/src/actions/renderActions.js index bc81f7b7..2359cbe9 100644 --- a/src/actions/renderActions.js +++ b/src/actions/renderActions.js @@ -1,5 +1,6 @@ export const RENDER_LOGIN = 'RENDER_LOGIN'; export const RENDER_FORM = 'RENDER_FORM'; +export const LOAD_SHEETLIST_DATA = 'LOAD_SHEETLIST_DATA'; export const RENDER_SHEETS = 'RENDER_SHEETS'; export const LOGOUT = 'LOGOUT'; @@ -8,6 +9,7 @@ export const displayLogin = (dispatch) => { type: RENDER_LOGIN, }); }; + export const displayForm = () => (dispatch) => { dispatch({ type: RENDER_FORM, diff --git a/src/components/BackButton/BackButton.jsx b/src/components/BackButton/BackButton.jsx index 92355827..231fefd5 100644 --- a/src/components/BackButton/BackButton.jsx +++ b/src/components/BackButton/BackButton.jsx @@ -9,7 +9,7 @@ import WIDGET_ID from '../../constants/index'; const BackButton = props => ( - - +
+ { GoodFormValues ? ( +
+ + + + + + + +
+ ) : ( +
+ + + + + + + + + + )} +
+ + ); } } diff --git a/src/components/SheetList/SheetList.jsx b/src/components/SheetList/SheetList.jsx new file mode 100644 index 00000000..c6266713 --- /dev/null +++ b/src/components/SheetList/SheetList.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { List } from 'semantic-ui-react'; +import { connect } from 'react-redux'; +import SheetTab from '../SheetTab/SheetTab'; +import injectWidgetId from '../../utils/utils'; +import WIDGET_ID from '../../constants/index'; +/* eslint-disable max-len, react/forbid-prop-types */ + +export const SheetList = ({ sheetList }) => ( + + {sheetList.map(sheet => )} + +); + +SheetList.defaultProps = { + widgetId: WIDGET_ID, +}; +SheetList.propTypes = { + sheetList: PropTypes.array.isRequired, +}; +const mapStateToProps = (state, ownProps) => { + const id = ownProps.widgetId; + const sheetList = state.widgets.byId[id].sheet.sheetList; + return { + sheetList, + }; +}; + +export default injectWidgetId(connect(mapStateToProps)(SheetList)); diff --git a/src/components/SheetTab/SheetTab.jsx b/src/components/SheetTab/SheetTab.jsx new file mode 100644 index 00000000..effb113d --- /dev/null +++ b/src/components/SheetTab/SheetTab.jsx @@ -0,0 +1,54 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { List } from 'semantic-ui-react'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { fetchSheet } from '../../actions'; +import injectWidgetId from '../../utils/utils'; +import WIDGET_ID from '../../constants/index'; +/* eslint-disable no-shadow, max-len */ + + +export class SheetTab extends Component { + getChildContext() { + return { sheetName: this.props.sheetName }; + } + render() { + const { spreadsheetId, sheetName, fetchSheet } = this.props; + return ( + fetchSheet(spreadsheetId, sheetName)}> + {sheetName} + + ); + } +} + + +SheetTab.propTypes = { + fetchSheet: PropTypes.func.isRequired, + spreadsheetId: PropTypes.string.isRequired, + sheetName: PropTypes.string.isRequired, +}; + +SheetTab.defaultProps = { + widgetId: WIDGET_ID, +}; +SheetTab.childContextTypes = { + sheetName: PropTypes.string, +}; +export const mapStateToProps = (state, ownProps) => { + const id = ownProps.widgetId; + const sheetName = ownProps.sheetName; + const spreadsheetId = state.widgets.byId[id].sheet.spreadsheetId; + + return { + spreadsheetId, + sheetName, + }; +}; +const mapDispatchToProps = dispatch => bindActionCreators({ + fetchSheet, +}, dispatch); + + +export default injectWidgetId(connect(mapStateToProps, mapDispatchToProps)(SheetTab)); diff --git a/src/reducers/index.js b/src/reducers/index.js index 72ad9c66..86a72f97 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,12 +1,14 @@ import { combineReducers } from 'redux'; import { SHEET_RETRIEVED, STORE_SHEET_DATA } from '../actions'; +import { LOAD_SHEETLIST_DATA } from '../actions/renderActions'; import WIDGET_ID from '../constants/index'; import currentPage from './renderReducer'; /* eslint-disable max-len */ -function sheet(state = { range: '', majorDimension: '', header: { headerCellIds: [], headerCellsById: {} }, rowIds: [], rowsById: {}, spreadsheetId: '', sheetName: '' }, action) { +function sheet(state = { range: '', majorDimension: '', header: { headerCellIds: [], headerCellsById: {} }, rowIds: [], rowsById: {}, spreadsheetId: '', sheetName: '', sheetList: [] }, action) { switch (action.type) { case SHEET_RETRIEVED: return { + ...state, range: action.sheetData.sheets.sheets[0].range, majorDimension: action.sheetData.sheets.sheets[0].majorDimension, header: { headerCellIds: action.sheetData.sheets.sheets[0].values[0].map((value, idx) => idx + 1), @@ -33,6 +35,11 @@ function sheet(state = { range: '', majorDimension: '', header: { headerCellIds: spreadsheetId: action.spreadsheetId, sheetName: action.sheetName, }; + case LOAD_SHEETLIST_DATA: + return { + ...state, + sheetList: action.sheetList, + }; default: return state; }