From e84da6ac6e6597de7994ad3a688592be54129b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Picolo?= <jpp18@inf.ufpr.br> Date: Thu, 25 Feb 2021 09:27:12 -0300 Subject: [PATCH] Adds table and mockup route --- src/App.js | 4 +- src/components/Pages/OfferPageComponent.js | 19 +- src/components/TableComponent.js | 327 ++++++++------------- src/css/TableComponent.scss | 85 ++---- src/data/groups.js | 27 +- 5 files changed, 192 insertions(+), 270 deletions(-) diff --git a/src/App.js b/src/App.js index 23a3043..d80810e 100644 --- a/src/App.js +++ b/src/App.js @@ -54,7 +54,7 @@ function App() { <Route exact path={routes.formacao_licenciatura} render={props => <TrainingPageComponent {...props} />} /> <Route exact path={routes.formacao_pos_graduacao} render={props => <TrainingPageComponent {...props} />} /> <Route exact path={routes.nivel_formacao_docente} render={props => <TrainingPageComponent {...props} />} /> - <Route exact path={routes.situacao_matricula} render={props => <OfferPageComponent {...props} />} /> + <Route exact path={routes.situacao_matriculas} render={props => <OfferPageComponent {...props} />} /> <Route exact path={routes.contato} component={ContatoComponent} /> <Route exact path={routes.acessibilidade} component={AccessibilityComponent} /> <Route exact path={routes.mapa_site} component={SiteMapComponent} /> @@ -65,5 +65,5 @@ function App() { </Router> ); } - +// export default App; diff --git a/src/components/Pages/OfferPageComponent.js b/src/components/Pages/OfferPageComponent.js index 62c7536..d0ecdab 100644 --- a/src/components/Pages/OfferPageComponent.js +++ b/src/components/Pages/OfferPageComponent.js @@ -27,6 +27,7 @@ import { getIndicator } from '../../data/groups'; import { getIndicatorsGroup } from '../../data/indicadores'; import MapComponent from '../Map/MapComponent'; +import TableComponent from '../TableComponent'; import ExpandableGroupComponent from '../ExpandableGroupComponent'; import IndicatorsComponent from '../Dropdown/IndicatorsComponent'; import FiltersGroupComponent from '../Dropdown/FiltersGroupComponent'; @@ -226,7 +227,7 @@ function OfferPageComponent(props) { } } - //const chartsDim = (indicator.chartsDim) ? indicator.chartsDim : routeDim + const groupInfo = getIndicator(path[2], indicatorName); return ( <Grid container direction="column" className={`page-container ${contrastSet ? "high-contrast" : ""}`}> {(filtersLoading === true || filtersLoading === undefined) ? null : @@ -259,7 +260,6 @@ function OfferPageComponent(props) { currentindicador={indicator} contrastSet={contrastSet}> </FixedFiltersComponent> - {/* <SelectDimComponent setLocked={setMapBorderDimLocked} setDim={setMapBorderDim}></SelectDimComponent> */} </Grid> <Grid item xs={12} sm={12} md={12}> <MapComponent @@ -283,7 +283,7 @@ function OfferPageComponent(props) { <Grid item xs={12}> {filtersLoading !== false ? null : <ExpandableGroupComponent - groupInfo={getIndicator(path[2], indicatorName)} + groupInfo={groupInfo} hashFilters={hashFilters} location={chartLocation} sideChart={true} @@ -298,12 +298,23 @@ function OfferPageComponent(props) { {filtersLoading !== false ? null : <ExpandableGroupComponent - groupInfo={getIndicator(path[2], indicatorName)} + groupInfo={groupInfo} hashFilters={hashFilters} contrastSet={contrastSet} > </ExpandableGroupComponent> } + + + {filtersLoading !== false ? null : + groupInfo.tables.map((table, idx) => ( + <TableComponent + hashFilters={hashFilters} + location={chartLocation} + tableInfo={table} + /> + )) + } </Grid> ) } diff --git a/src/components/TableComponent.js b/src/components/TableComponent.js index eed78e1..a49c64a 100644 --- a/src/components/TableComponent.js +++ b/src/components/TableComponent.js @@ -1,221 +1,142 @@ import React, { useState, useEffect } from 'react'; -import BaseTable, { Column, AutoResizer } from 'react-base-table' -import 'react-base-table/styles.css' -import Consult from './Consult' -import '../css/TableComponent.scss' -import BlockUi from 'react-block-ui'; -import { Loader } from 'react-loaders'; -import { downloadComponent } from '../data/shared' +import { + Grid, withStyles, + Table, TableBody, + TableCell, TableContainer, + TableHead, TableRow, + TableFooter, Paper +} from '@material-ui/core'; -let ConsultHandler = new Consult() +import Consult from './Consult'; +import '../css/TableComponent.scss'; -function TableComponent(props) { - const { description, filtersEducation, filtersLocation } = props - - const [componentId, setComponentId] = useState(null) - - useEffect(() => { - if (componentId === null) { - setComponentId('table' + ConsultHandler.generateComponentId()) - } - }, [componentId]) - - const [data, setData] = useState([]) - const [baseData, setBaseData] = useState([]) - const [columns, setColumns] = useState([]) - const [loading, setLoading] = useState(true) - - const [downloadingType, setDownloadingType] = useState(false) // Used to extend table before downloading it as image - useEffect(() => { - let isMounted = true // flag denoutes mounted status - const yearFilters = {min_year: {value: description.years[0]}, max_year: {value: description.years.length > 1 ? description.years[1] : description.years[0]}} +let ConsultHandler = new Consult(); +let componentId = null; +let filtersEducation = null; +let filtersLocation = null; +let year = null; - ConsultHandler.getMultipleRoutes(description.route, setBaseData, setLoading, 'array', description.dims, [filtersEducation, filtersLocation, yearFilters, description.extraFilters], [], isMounted) - - return () => { isMounted = false } - }, [description, filtersEducation, filtersLocation]) +function TableComponent(props) { + const { tableInfo, hashFilters, location } = props; + const [tableRows, setTableRows] = useState([]); + const [footerRow, setFooterRow] = useState([]); + const [tableData, setTableData] = useState([]); + const [loadingData, setLoadingData] = useState(true); + + if (componentId === null) { + componentId = ConsultHandler.generateComponentId() + } useEffect(() => { - if (loading === false) { - let formattedData = [] - - let cols = [{ 'title': description.dimTitle, 'key': 'name', 'id': undefined, 'total': 0}] - - if (description.extraDimCols) { // Single dim uses 2+ collumns, like in localoffer - description.extraDimCols.forEach((col) => { - cols.push({'title': [col.title], 'key': col.key}) - }) - } - - for (let i = 0; i < description.route.length; i++) { - const result = baseData[i] - const dims = description.dims[i] - - let dim1id = dims[0] + '_id' - let dim1name = dims[0] + '_name' - if (dims[0] === 'year') dim1id = dim1name = 'year' + const tableYearFilters = { min_year: {value: tableInfo.years[0]}, max_year: {value: tableInfo.years[1]} } + filtersEducation = hashFilters.get[tableInfo.education] + filtersLocation = hashFilters.get.location + year = tableInfo.years[1] + + ConsultHandler.fetchVar('tableData' + componentId, setTableData, setLoadingData, tableInfo.route, tableInfo.dim, [tableYearFilters, filtersLocation, filtersEducation, tableInfo.extraFilters]); + + }, [filtersEducation, filtersLocation, tableInfo]); + + // headers = {[ + // "Dessert", "Calories", "Fat (g)", "Carbs (g)", "Protein" + // ]} + // rows = {[ + // ['Frozen yoghurt', 159, 6.0, 24, 4.0], + // ['Ice cream sandwich', 237, 9.0, 37, 4.3], + // ['Eclair', 262, 16.0, 24, 6.0], + // ['Cupcake', 305, 3.7, 67, 4.3], + // ['Gingerbread', 356, 16.0, 49, 3.9], + // ]} - let dim2id = dims[1] + '_id' - let dim2name = dims[1] + '_name' - if (dims[1] === 'year') dim2id = dim2name = 'year' - - let totalCol = { 'title': 'Total', 'key': 'total' + i, 'total': 0} - - result.forEach(d => { - let colKey = 't' + description.route[i] + d[dim2id] - let currentCol = cols.find(e => e.key === colKey) - if (!currentCol && dims.length > 1) { - currentCol = { 'title': d[dim2name], 'key': colKey, 'total': 0} - cols.push(currentCol) - } - - const elementId1 = d[dim1id] - let row = formattedData.find(e => e.id === elementId1) - if (row === undefined) { - row = { 'name': d[dim1name], 'id': elementId1, 'total': 0 } - - if (description.extraDimCols) { // Single dim uses 2+ collumns, like in localoffer - description.extraDimCols.forEach((col) => { - row[col.key] = d[col.key + '_name'] - }) - } - - formattedData.push(row) - } - - row[colKey] = d.total - - row['total' + i] = (row['total' + i] !== undefined) ? row['total' + i] + d.total : d.total - - totalCol.total += d.total - - if (dims.length > 1 ) currentCol.total += d.total - }) - - cols.push(totalCol) + useEffect(() => { + if(!loadingData) { + // Fill rows + let new_rows = [] + let tableLength = tableData.length; + let i; + for(i = 0; i < tableLength; i++) { + let item = tableData[i]; + let row = [] + row.push(item["pos_training_name"]); + row.push(item["total"]); + new_rows.push(row); } - let totalRow = { 'name': 'Total', 'total': 0 } - - const colWidthFactor = (description.extraDimCols) ? 1/(cols.length) : 1/(cols.length+1) - - cols.forEach(c => { - totalRow.total += c.total - - if (c.key !== 'name') { - totalRow[c.key] = (totalRow[c.key] !== undefined) ? totalRow[c.key] + c.total : c.total - c.widthFactor = colWidthFactor; - } else { - if (description.extraDimCols) c.widthFactor = colWidthFactor; - else c.widthFactor = colWidthFactor*2; + setTableRows(new_rows) + + // Fill footer + let size = tableInfo.headers.length; + let final = new Array(size).fill(0); + final[0] = "Total" + let j; + let n_rows = new_rows.length; + for(j = 0; j < n_rows; j++) { + let row = new_rows[j] + for(i = 1; i < size; i++) { + final[i] = parseInt(final[i]) + parseInt(row[i]) ; } - }) - formattedData.push(totalRow) - - setData(formattedData) - setColumns(cols) - } - }, [loading, baseData, description]) - - useEffect(() => { - if (downloadingType !== false) { - downloadComponent(componentId, downloadingType).then(setDownloadingType(false)) - } - }, [downloadingType]) - - function getTitle() { - let dataLocationDims = ['city', 'microregion', 'mesoregion', 'university'] - if (description.education === 'basic') dataLocationDims.unshift('school') - else dataLocationDims.unshift('campi') - - if (props.loading === true) - return 'Loading...' - - const filters = props.filters - let name = "Paraná" - - for (let i = 0; i < dataLocationDims.length; i++) { - const key = dataLocationDims[i] - - if (filtersLocation[key] !== undefined && typeof filtersLocation[key].value === "number" && filtersLocation[key].value > 0) { - name = filtersLocation[key].data.find((e) => { return e.id === filtersLocation[key].value }).name - break; } + setFooterRow(final); } - let title = name + ', ' + description.years[0] - if (description.years && description.years.length === 2) title += ' - ' + description.years[1] - return title - } - - function getCSV() { - let csv = []; - let row = []; - - columns.forEach((col) => { - row.push(col.title); - }); - - csv.push(row.join(';')); - - data.forEach((data) => { - row = []; - columns.forEach((col) => { - let value = data[col.key]; - if (typeof value === 'undefined' || value === null) value = ''; - value = value.toString().replace(/\"/g, ''); - row.push(`"${value}"`); - }); - - csv.push(row.join(';')); - }); - - var link = document.createElement("a"); - var file = new Blob([csv.join('\n')], {type: 'text/csv'}); - var url = URL.createObjectURL(file); - - link.href = url; - link.download = description.title + ".csv"; - - link.click(); - } + }, [loadingData]) + + const StyledTableCell = withStyles(() => ({ + head: { + backgroundColor: 'blue', + color: 'white', + }, + footer: { + backgroundColor: 'blue', + color: 'white', + }, + body: { + fontSize: 15, + }, + }))(TableCell); return ( - (data === undefined) ? null : - <BlockUi id={componentId} tag="div" blocking={loading} loader={<Loader active type="ball-spin-fade-loader" color="deepskyblue"/>}> - <button className="download-button" onClick={() => setDownloadingType('pdf')}>PDF</button> - <button className="download-button" onClick={() => getCSV()}>CSV</button> - - <div className="table-title-container"> - <p className="table-title">{description.title}</p> - <p className="table-title">{getTitle()}</p> - </div> - - <AutoResizer height={(data.length <= 12 || (downloadingType !== false && data.length < 500) ) ? (data.length * 40 + 50) : 530}> - {({ width, height }) => ( - width = width-20, - <div className="table-container" key={'header-container' + description.title}> - {(description.route.length > 1 && columns.length > 0) ? - <BaseTable className="custom-column-header" width={width} height={50} rowHeight={40} key={description.title}> - <Column key='blank' width={width * columns[0].widthFactor} ></Column> - {description.tableTitles.map((title) => ( - <Column key={title} width={(width - (width * columns[0].widthFactor))/description.route.length} title={title}></Column> - ))} - </BaseTable> - : null} - <BaseTable width={width} height={height} data={data} rowHeight={40}> - {columns.map(col => ( - <Column key={col.key} dataKey={col.key} width={width * col.widthFactor} title={col.title} /> - ))} - </BaseTable> - </div> - )} - </AutoResizer> - - {description.notes.map((note, i) => ( - <p className="table-note" key={"note-" + i}>{note}</p> - ))} - </BlockUi> + <Grid className={'root-container'} component={Paper}> + <Grid className="title-container"> + <h4>{ tableInfo.title }</h4> + </Grid> + + <Grid className="subtitle-container"> + <p>{`${location} - ${year}`}</p> + </Grid> + + <TableContainer className={'table-container'} component={Paper}> + <Table aria-label="customized table"> + <TableHead> + <TableRow> + {tableInfo.headers.map((header, idxHeader) => ( + <StyledTableCell key={idxHeader}>{ header }</StyledTableCell> + ))} + </TableRow> + </TableHead> + <TableBody> + {tableRows.map((row, idx) => ( + <TableRow key={idx}> + {row.map((item, idxRow) => ( + <StyledTableCell key={idxRow}>{ item }</StyledTableCell> + ))} + </TableRow> + ))} + </TableBody> + <TableFooter> + <TableRow> + {footerRow.map((header, idxHeader) => ( + <StyledTableCell key={idxHeader}>{ header }</StyledTableCell> + ))} + </TableRow> + </TableFooter> + </Table> + </TableContainer> + + <Grid className="source-container"> + <p> { tableInfo.notes }</p> + </Grid> + </Grid> ) } diff --git a/src/css/TableComponent.scss b/src/css/TableComponent.scss index 6315560..0f01b1b 100644 --- a/src/css/TableComponent.scss +++ b/src/css/TableComponent.scss @@ -1,56 +1,33 @@ -.table-title { - color: #45D4E8; - margin-top: 0; - margin-bottom: 0; - /* font-family: "Univers LT Std"; - font-style: bold; */ - font-size: 17px; - /* min-width: max-content; */ - text-align: center; -} +.root-container { + margin-bottom: 2%; + + .table-container { + max-width: 96%; + margin: 2%; + + .MuiTableHead-root { + .MuiTableRow-root { + border-top-left-radius: 30px; + border-top-right-radius: 30px; + } + } + } + + .title-container { + font-size: 18px; + text-align: center; + } -.table-title-container { - padding-top: 10px; - padding-bottom: 10px; -} + .subtitle-container { + font-size: 16px; + text-align: center; + margin-top: -1%; + } -.table-note { - font-size: 12px; - color: #5a5a5a; - line-height: 1.42; - padding: 5px 10px 5px 10px; - margin: 0; -} - -.table-container { - padding: 10px; -} - -.custom-column-header .BaseTable__header .BaseTable__header-row{ - background-color: #ddd; -} - -.download-button { - border: 1px solid #ccc !important; - border-bottom: 2px solid rgba(0,0,0,0.2) !important; - background-color: white; - border-bottom-left-radius: 4px !important; - border-bottom-right-radius: 4px !important; - border-radius: 4px; - display: inline-block; - cursor: pointer; - font-family: Arial; - font-size: 15px; - padding: 5px; - text-decoration:none; - margin: 5px; -} - -.download-button:hover { - background-color:#eee; -} - -.download-button:active { - position: relative; - top: 1px; -} + .source-container { + margin-bottom: 4%; + margin-left: 2%; + color: grey; + font-size: 13px; + } +} \ No newline at end of file diff --git a/src/data/groups.js b/src/data/groups.js index d75d8e3..7cd451b 100644 --- a/src/data/groups.js +++ b/src/data/groups.js @@ -20,7 +20,7 @@ const groups = { pieCharts: [ { title: 'NÚMERO E PERCENTUAL DE DOCENTES SEGUNDO NÍVEL DE FORMAÇÃO', - route: 'teacher', + route: ['teacher'], dim: 'initial_training', type: 'bar', years: [2019, 2019], @@ -49,7 +49,7 @@ const groups = { }, { title: 'PERCENTUAL DE ADEQUAÇÃO DA FORMAÇÃO DOCENTE', - route: 'disciplines', + route: ['disciplines'], dim: 'discipline', type: 'bar', years: [2012, 2019], @@ -61,7 +61,7 @@ const groups = { pieCharts: [ { title: 'PERCENTUAL DE ADEQUAÇÃO DA FORMAÇÃO DOCENTE', - route: 'disciplines', + route: ['disciplines'], dim: 'discipline', type: 'bar', years: [2019, 2019], @@ -90,7 +90,7 @@ const groups = { pieCharts: [ { title: 'NÚMERO E PERCENTUAL DE DOCENTES SEGUNDO NÍVEL DE FORMAÇÃO FORMAÇÃO', - route: 'teacher', + route: ['teacher'], dim: 'licentiate_degree', type: 'bar', years: [2019, 2019], @@ -100,7 +100,7 @@ const groups = { }, { title: 'NÚMERO DE DOCENTES SEGUNDO NÍVEL DE FORMAÇÃO', - route: 'teacher', + route: ['teacher'], dim: 'licentiate_degree', type: 'bar', years: [2019, 2019], @@ -129,7 +129,7 @@ const groups = { pieCharts: [ { title: 'NÚMERO E PERCENTUAL DE DOCENTES SEGUNDO NÍVEL DE FORMAÇÃO FORMAÇÃO', - route: 'teacher', + route: ['teacher'], dim: 'pos_training', type: 'bar', years: [2019, 2019], @@ -162,12 +162,25 @@ const groups = { pieCharts: [ { title: 'NÚMERO E PERCENTUAL DE DOCENTES SEGUNDO NÍVEL DE FORMAÇÃO FORMAÇÃO', - route: 'teacher', + route: ['teacher'], + dim: 'pos_training', + type: 'bar', + years: [2019, 2019], + notes: ['Fonte: Elaborado pelo Laboratório de Dados Educacionais a partir dos Microdados do Censo de Educação Superior/INEP 2018.'], + education: 'basic', + extraFilters: {state: {value: 41}}, + } + ], + tables: [ + { + title: 'NÚMERO E PERCENTUAL DE DOCENTES SEGUNDO NÍVEL DE FORMAÇÃO FORMAÇÃO', + route: ['teacher'], dim: 'pos_training', type: 'bar', years: [2019, 2019], notes: ['Fonte: Elaborado pelo Laboratório de Dados Educacionais a partir dos Microdados do Censo de Educação Superior/INEP 2018.'], education: 'basic', + headers: ['Disciplina', 'Quantidade'], extraFilters: {state: {value: 41}}, } ], -- GitLab