diff --git a/components/admin-drawer/component.js b/components/admin-drawer/component.js index 11915e9..f6f6c09 100644 --- a/components/admin-drawer/component.js +++ b/components/admin-drawer/component.js @@ -51,7 +51,12 @@ const buttons = [ "key":3, 'name':'Usuarios', 'value':'users' - } + }, + { + "key":4, + 'name':'Metricas', + 'value':'metric' + } ] diff --git a/components/admin-metric/component.js b/components/admin-metric/component.js new file mode 100644 index 0000000..ee171e3 --- /dev/null +++ b/components/admin-metric/component.js @@ -0,0 +1,182 @@ +import React, { Component } from 'react' +import styled from 'styled-components' + +import Icon from 'react-icons-kit' +import { caretDown } from 'react-icons-kit/fa/caretDown' +import { caretRight } from 'react-icons-kit/fa/caretRight' +import getConfig from 'next/config' +import WithDocumentTagsContext from '../document-tags-context/component' +import TitleContent from '../title-content-admin/component' +import MetricsByAuthor from './metricsByAuthor' +import MetricsUsers from './metricsUsers' +import MetricsUsersByRole from './metricsUsersByRole' +import MetricsInteractions from './metricsInteractions' +import MetricsTags from './metricsTags' +import WithUserContext from '../with-user-context/component' + +const { publicRuntimeConfig: { API_URL } } = getConfig() + +const StyledMetricAdmin = styled.div` + width:100% + font-size: 1.4rem; +` + +const SectionsWrapper = styled.div` +display: flex; +flex-direction: column; +width: 100%; +` + +const SectionHeader = styled.div` +display:flex; +justify-content: space-between; +padding: 7px 7px; +// color: #454246; +// border: 1px solid #eaeaea; +background-color: #5c97bc; +color: #FFF; +// border-radius: 5px; +width: 100%; +cursor: pointer; +margin-top: 20px; +span { + font-family: var(--bold); + font-size: 1.6rem; +} +` + +const TitleMetric = styled.div` + display:flex; + justify-content: space-between; + color: #5c97bc; + padding: 5px 0px; + border-bottom: 2px solid #5c97bc; + width: 100%; + margin-top: 8px; + margin-bottom: 8px; + font-family: var(--bold); + font-weight: bold; + font-size: 1.6rem; +` + +const DownloadButton = styled.button` + background-color: #5c97bc; + color: #FFF; + border: none; + padding: 4px 10px; + border-radius: 4px; + cursor: pointer; + &:hover { + background-color: #2c4c61; + } +` + +class MetricAdmin extends Component { + constructor (props) { + super(props) + this.state = { + tags: [], + showMetricByAuthor: false, + showMetricByTags: false, + showMetricUsers: false, + showMetricInteractions: false + } + } + + componentDidMount () { + + } + + downloadXls = async (theUrl, filename) => { + try { + const result = await fetch(`${theUrl}`,{ + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + } + }) + const blob = await result.blob() + + // Download API Files With React & Fetch - https://medium.com/yellowcode/download-api-files-with-react-fetch-393e4dae0d9e + const url = window.URL.createObjectURL(new Blob([blob])); + const link = document.createElement('a'); + link.href = url; + // set the name as YYYYMMDD_HHmmss.xls + const date = new Date() + const dateString = `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}_${date.getHours()}${date.getMinutes()}${date.getSeconds()}` + link.setAttribute('download', `${dateString}_${filename}.xlsx`); // 3. Append to html page + document.body.appendChild(link); // 4. Force download + link.click(); // 5. Clean up and remove the link + link.parentNode.removeChild(link); + + } catch (err) { + console.error(err) + } + } + + + render () { + const { showMetricByAuthor, showMetricByTags, showMetricUsers, showMetricInteractions } = this.state + return ( + + Metricas + + this.setState({ showMetricByAuthor: !this.state.showMetricByAuthor })}> + Metricas por autor + + + { + showMetricByAuthor && + } + this.setState({ showMetricByTags: !this.state.showMetricByTags })}> + Metricas por etiquetas + + + { + showMetricByTags && + } + this.setState({ showMetricInteractions: !this.state.showMetricInteractions })}> + Metricas de interacciónes en proyectos + + + { + showMetricInteractions && ( +
+ +
+ ) + } + this.setState({ showMetricUsers: !this.state.showMetricUsers })}> + Metricas de usuarios + + + { + showMetricUsers && ( +
+ + +
+ ) + } +
+
+
+ + Descarga de datasets + +
+

Listado completo de usuarios

+ this.downloadXls(`${API_URL}/api/v1/metric/users/xls`, 'usuarios')}>Descargar dataset +

+

Listado completo de proyectos con sus autores e interacciones

+ this.downloadXls(`${API_URL}/api/v1/metric/interactions/xls`, 'proyectos')}>Descargar dataset +
+
+ ) + } +} + +MetricAdmin.propTypes = { +} + +export default WithUserContext(MetricAdmin) diff --git a/components/admin-metric/metricsByAuthor.js b/components/admin-metric/metricsByAuthor.js new file mode 100644 index 0000000..06115b1 --- /dev/null +++ b/components/admin-metric/metricsByAuthor.js @@ -0,0 +1,318 @@ +import React, { Component } from 'react' +import styled from 'styled-components' +import Link from 'next/link' +import getConfig from 'next/config' +import WithUserContext from '../../components/with-user-context/component' + +const { publicRuntimeConfig: { API_URL } } = getConfig() + +const MetricWrapper = styled.div` + padding: 15px 0; +` + +const LoadingAnimation = styled.div` + width: 100%; + display: flex; + justify-content: center; + align-items: center; + background: #eaeaea; + padding: 20px 5px; + margin-bottom: 5px; + // animate text loading to fade in and out + p { + animation: fadeInOut 1.5s ease-in-out infinite; + } + @keyframes fadeInOut { + 0% { + opacity: 0; + } + 45% { + opacity: 1; + } + 100% { + opacity: 0; + } + } +` + +const UserWrapper = styled.div` + display: flex; + align-items: center; + flex-direction: row; +` +const UserAvatar = styled.div` + height: 32px; + width: 32px; + margin-right: 10px; + border-radius: 50%; + border: 1px solid #eaeaea; + background-image: url('${(props) => props.userId ? `${API_URL}/api/v1/users/${props.userId}/avatar` : '/static/assets/userdefault.png'}'); + background-size: cover; + background-position: center; +` +const UserData = styled.div` + +` +const UserName = styled.p` + font-weight: bold; + color: #5c97bc; +` + +const UserEmail = styled.p` + color: #777; + font-size: 1rem; +` + +const DataTable = styled.table` + width: 100%; + border-collapse: collapse; + border: 1px solid #eaeaea; + thead { + tr { + th { + text-align: left; + padding: 10px 5px; + border-bottom: 2px solid #eaeaea; + } + } + } + tbody { + tr { + td { + padding: 5px 5px + border-bottom: 1px solid #eaeaea; + } + } + } +` + +const ButtonsWrapper = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + margin-top: 20px; + button { + margin: 0 5px; + padding: 5px 10px; + border-radius: 4px; + border: 1px solid #2c4c61; + cursor: pointer; + color: #2c4c61; + &:hover { + background-color: #2c4c61; + color: #FFF; + } + &:first-child { + margin-left: 0; + } + &:last-child { + margin-right: 0; + } + &.disabled { + color: #777; + border-color: #777; + } + } +` + +const FilterRow = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + margin-bottom: 5px; + border: 1px solid #eaeaea; + padding: 10px; + border-radius: 4px; + p { + margin-right: 10px; + } + select { + padding: 5px 10px; + border-radius: 4px; + border: 1px solid #2c4c61; + cursor: pointer; + color: #2c4c61; + } +` + +class MetricsByAuthor extends Component { + constructor (props) { + super(props) + this.state = { + yearList: [], + selectedYear: '', + isLoading: true, + projectsByUserList: [], + projectsByPage: [], + total: 0, + page: 1, + limit: 10, + totalPages: 0 + } + } + + componentDidMount () { + // complete a year list from 2017 to current year + const currentYear = new Date().getFullYear() + const yearList = [] + for (let i = 2019; i <= currentYear; i++) { + yearList.push(i) + } + this.setState({ yearList }) + this.init() + } + + init () { + fetch(`${API_URL}/api/v1/metric/documentByAuthors`, { + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + }) + .then((res) => res.json()) + .then((data) => { + this.setState((prevState) => { + return { + projectsByUserList: data, + isLoading: false, + projectsByPage: data.slice(prevState.page - 1, prevState.limit), + total: data.length, + totalPages: Math.ceil(data.length / prevState.limit) + } + }) + }) + .catch((err) => console.error(err)) + } + + nextPage () { + this.setState((prevState) => { + return { + page: prevState.page + 1, + projectsByPage: prevState.projectsByUserList.slice(prevState.page * prevState.limit, prevState.page * prevState.limit + prevState.limit) + } + }) + } + + prevPage () { + this.setState((prevState) => { + return { + page: prevState.page - 1, + projectsByPage: prevState.projectsByUserList.slice((prevState.page - 2) * prevState.limit, (prevState.page - 2) * prevState.limit + prevState.limit) + } + }) + } + + changeYear (e) { + const year = e.target.value + this.setState({ isLoading: true }) + this.fetchByYear(year) + } + + fetchByYear (year) { + let selectedYear = year + let query = '' + if (selectedYear) { + query = `?year=${selectedYear}` + } + fetch(`${API_URL}/api/v1/metric/documentByAuthors${query}`, { + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + }) + .then((res) => res.json()) + .then((data) => { + this.setState((prevState) => { + return { + projectsByUserList: data, + isLoading: false, + projectsByPage: data.slice(prevState.page - 1, prevState.limit), + total: data.length, + totalPages: Math.ceil(data.length / prevState.limit), + page: 1, + selectedYear: selectedYear + } + }) + }) + .catch((err) => console.error(err)) + } + + render () { + const { projectsByPage, isLoading, page, totalPages } = this.state + return ( + + +

Filtrar por año

+ +
+ { isLoading && ( + +

Cargando...

+
+ )} + { !isLoading && (
+ + + + Usuario + Cantidad de proyectos + + + + { + projectsByPage.map((userData) => { + return ( + + + + + + {userData.user.fullname} + {userData.user.email} + + + + {userData.count} + + ) + }) + } + + + + + + + Página {page} de {totalPages} + + +
+ ) } + +
+ ) + } +} + +export default WithUserContext(MetricsByAuthor) diff --git a/components/admin-metric/metricsInteractions.js b/components/admin-metric/metricsInteractions.js new file mode 100644 index 0000000..132aba4 --- /dev/null +++ b/components/admin-metric/metricsInteractions.js @@ -0,0 +1,461 @@ +import React, { Component } from 'react' +import styled from 'styled-components' +import Link from 'next/link' +import getConfig from 'next/config' +import WithUserContext from '../../components/with-user-context/component' + +const { publicRuntimeConfig: { API_URL } } = getConfig() + +const MetricWrapper = styled.div` + padding: 15px 0; +` + +const LoadingAnimation = styled.div` + width: 100%; + display: flex; + justify-content: center; + align-items: center; + background: #eaeaea; + padding: 20px 5px; + margin-bottom: 5px; + // animate text loading to fade in and out + p { + animation: fadeInOut 1.5s ease-in-out infinite; + } + @keyframes fadeInOut { + 0% { + opacity: 0; + } + 45% { + opacity: 1; + } + 100% { + opacity: 0; + } + } +` + +const UserWrapper = styled.div` + display: flex; + align-items: center; + flex-direction: row; +` +const UserAvatar = styled.div` + height: 32px; + width: 32px; + margin-right: 10px; + border-radius: 50%; + border: 1px solid #eaeaea; + background-image: url('${(props) => props.userId ? `${API_URL}/api/v1/users/${props.userId}/avatar` : '/static/assets/userdefault.png'}'); + background-size: cover; + background-position: center; +` +const UserData = styled.div` + +` +const UserName = styled.p` + font-weight: bold; + color: #5c97bc; +` + +const UserEmail = styled.p` + color: #777; + font-size: 1rem; +` + +const ProjectWrapper = styled.div` + display: flex; + align-items: flex-start; + flex-direction: column; +` + +const ProjectName = styled.p` + font-weight: bold; + color: #5c97bc; +` + +const ProjectDescription = styled.p` + color: #777; + font-size: 1rem; +` + +const DataTable = styled.table` + width: 100%; + border-collapse: collapse; + border: 1px solid #eaeaea; + thead { + tr { + th { + text-align: left; + padding: 10px 5px; + border-bottom: 2px solid #eaeaea; + } + } + } + tbody { + tr { + td { + padding: 5px 5px + border-bottom: 1px solid #eaeaea; + } + } + } +` + +const ButtonsWrapper = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + margin-top: 20px; + button { + margin: 0 5px; + padding: 5px 10px; + border-radius: 4px; + border: 1px solid #2c4c61; + cursor: pointer; + color: #2c4c61; + &:hover { + background-color: #2c4c61; + color: #FFF; + } + &:first-child { + margin-left: 0; + } + &:last-child { + margin-right: 0; + } + &.disabled { + color: #777; + border-color: #777; + } + } +` + +const FilterRow = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + margin-bottom: 5px; + border: 1px solid #eaeaea; + padding: 10px; + border-radius: 4px; + p { + margin-right: 10px; + } + select { + padding: 5px 10px; + border-radius: 4px; + border: 1px solid #2c4c61; + cursor: pointer; + color: #2c4c61; + } +` + +class MetricsInteractions extends Component { + constructor (props) { + super(props) + this.state = { + yearList: [], + selectedYear: '', + isLoading: true, + projects: [], + accountableUsers: [], + tags: [], + selectedTag: '', + selectedAccountableUser: '', + projectsByPage: [], + total: 0, + page: 1, + limit: 10, + totalPages: 0 + } + } + + componentDidMount () { + // complete a year list from 2017 to current year + const currentYear = new Date().getFullYear() + const yearList = [] + for (let i = 2019; i <= currentYear; i++) { + yearList.push(i) + } + this.setState({ yearList }) + this.init() + } + + init () { + const fetchArrays = [] + fetchArrays.push(fetch(`${API_URL}/api/v1/metric/interactions`, { + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + })) + fetchArrays.push(fetch(`${API_URL}/api/v1/metric/usersByRole`, { + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + })) + fetchArrays.push(fetch(`${API_URL}/api/v1/document-tags/`, { + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + })) + Promise.all(fetchArrays) + .then((responses) => { + return Promise.all(responses.map((response) => { + return response.json() + })) + }) + .then((data) => { + const projects = data[0] + const usersAuthors = data[1].accountableUsers + const tags = data[2].results + this.setState((prevState) => { + return { + projects: projects, + accountableUsers: usersAuthors, + tags: tags, + isLoading: false, + projectsByPage: projects.slice(prevState.page - 1, prevState.limit), + total: projects.length, + totalPages: Math.ceil(projects.length / prevState.limit) + } + }) + }) + .catch((err) => console.error(err)) + + // fetch(`${API_URL}/api/v1/metric/interactions`, { + // headers: { + // Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + // 'Content-Type': 'application/json' + // }, + // method: 'GET' + // }) + // .then((res) => res.json()) + // .then((data) => { + // this.setState((prevState) => { + // return { + // projects: data, + // isLoading: false, + // projectsByPage: data.slice(prevState.page - 1, prevState.limit), + // total: data.length, + // totalPages: Math.ceil(data.length / prevState.limit) + // } + // }) + // }) + // .catch((err) => console.error(err)) + } + + nextPage () { + this.setState((prevState) => { + return { + page: prevState.page + 1, + projectsByPage: prevState.projects.slice(prevState.page * prevState.limit, prevState.page * prevState.limit + prevState.limit) + } + }) + } + + prevPage () { + this.setState((prevState) => { + return { + page: prevState.page - 1, + projectsByPage: prevState.projects.slice((prevState.page - 2) * prevState.limit, (prevState.page - 2) * prevState.limit + prevState.limit) + } + }) + } + + changeYear (e) { + const year = e.target.value + const tag = this.state.selectedTag + const accountableUser = this.state.selectedAccountableUser + this.setState({ isLoading: true }) + this.fetchByQuery(year, tag, accountableUser) + } + + changeTag (e) { + const year = this.state.selectedYear + const tag = e.target.value + const accountableUser = this.state.selectedAccountableUser + this.setState({ isLoading: true }) + this.fetchByQuery(year, tag, accountableUser) + } + + changeAccountableUser (e) { + const year = this.state.selectedYear + const tag = this.state.selectedTag + const accountableUser = e.target.value + this.setState({ isLoading: true }) + this.fetchByQuery(year, tag, accountableUser) + } + + isOpenOrClosed (closingDate) { + const today = new Date() + const closing = new Date(closingDate) + return today < closing + } + + fetchByQuery (year, tag, accountableUser) { + let queries = [] + let query = '' + if (year) { + queries.push(`year=${year}`) + } + if (tag) { + queries.push(`tag=${tag}`) + } + if (accountableUser) { + queries.push(`author=${accountableUser}`) + } + if (queries.length > 0) { + query = `?${queries.join('&')}` + } + fetch(`${API_URL}/api/v1/metric/interactions${query}`, { + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + }) + .then((res) => res.json()) + .then((data) => { + this.setState((prevState) => { + return { + projects: data, + isLoading: false, + projectsByPage: data.slice(prevState.page - 1, prevState.limit), + total: data.length, + totalPages: Math.ceil(data.length / prevState.limit), + page: 1, + selectedYear: year, + selectedTag: tag, + selectedAccountableUser: accountableUser + } + }) + }) + .catch((err) => console.error(err)) + } + + render () { + const { projectsByPage, isLoading, page, totalPages, tags, accountableUsers } = this.state + return ( + + +

Filtrar por año de creacion del proyecto

+ +
+ +

Filtrar por etiqueta

+ +
+ +

Filtrar por diputado

+ +
+ { isLoading && ( + +

Cargando...

+
+ )} + { !isLoading && (
+ + + + Proyecto + Autor + Apoyos + Comentarios + Aportes + Likes + Total Interacciones + + + + { + projectsByPage.map((project) => { + return ( + + + + {project.title} + Versión {project.version} - {project.tags && project.tags.length > 0 ? project.tags.map((t) => t.name).join(', ') : '(Sin tags)'} + Fecha creacion: {new Date(project.createdAt).toLocaleDateString('es-ES', { hour: 'numeric', minute: 'numeric' })} - {this.isOpenOrClosed(project.closingDate) ? Abierto : Cerrado} - {this.published ? Visible : Oculto} + + + + + + + {project.author.fullname} + {project.author.email} + + + + {project.apoyosCount} + {project.commentsFundationCount} + {project.commentsArticlesCount} + {project.likesCount} + {project.totalInteraction} + + ) + }) + } + + + + + + + Página {page} de {totalPages} + + +
+ ) } + +
+ ) + } +} + +export default WithUserContext(MetricsInteractions) diff --git a/components/admin-metric/metricsTags.js b/components/admin-metric/metricsTags.js new file mode 100644 index 0000000..8b1720d --- /dev/null +++ b/components/admin-metric/metricsTags.js @@ -0,0 +1,585 @@ +import React, { Component } from 'react' +import styled from 'styled-components' +import Link from 'next/link' +import getConfig from 'next/config' +import Icon from 'react-icons-kit' +import { thList } from 'react-icons-kit/fa/thList' +import WithUserContext from '../../components/with-user-context/component' + +const { publicRuntimeConfig: { API_URL } } = getConfig() + +const MetricWrapper = styled.div` + padding: 15px 0; +` + +const LoadingAnimation = styled.div` + width: 100%; + display: flex; + justify-content: center; + align-items: center; + background: #eaeaea; + padding: 20px 5px; + margin-bottom: 5px; + // animate text loading to fade in and out + p { + animation: fadeInOut 1.5s ease-in-out infinite; + } + @keyframes fadeInOut { + 0% { + opacity: 0; + } + 45% { + opacity: 1; + } + 100% { + opacity: 0; + } + } +` + +const UserWrapper = styled.div` + display: flex; + align-items: center; + flex-direction: row; +` +const UserAvatar = styled.div` + height: 32px; + width: 32px; + margin-right: 10px; + border-radius: 50%; + border: 1px solid #eaeaea; + background-image: url('${(props) => props.userId ? `${API_URL}/api/v1/users/${props.userId}/avatar` : '/static/assets/userdefault.png'}'); + background-size: cover; + background-position: center; +` +const UserData = styled.div` + +` +const UserName = styled.p` + font-weight: bold; + color: #5c97bc; +` + +const UserEmail = styled.p` + color: #777; + font-size: 1rem; +` + +const ProjectWrapper = styled.div` + display: flex; + align-items: flex-start; + flex-direction: column; +` + +const ProjectName = styled.p` + font-weight: bold; + color: #5c97bc; +` + +const ProjectDescription = styled.p` + color: #777; + font-size: 1rem; +` + +const TagWrapper = styled.div` + display: flex; + align-items: flex-start; + flex-direction: column; +` + +const TagName = styled.p` + font-weight: bold; + // color: #5c97bc; + font-size: 1.2rem; +` + +const TagDescription = styled.p` + color: #777; + font-size: 1rem; +` + +const DataTable = styled.table` + width: 100%; + border-collapse: collapse; + border: 1px solid #eaeaea; + thead { + tr { + th { + text-align: left; + padding: 10px 5px; + border-bottom: 2px solid #eaeaea; + } + } + } + tbody { + tr { + td { + padding: 5px 5px + border-bottom: 1px solid #eaeaea; + } + } + } +` + +const ButtonsWrapper = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + margin-top: 20px; + button { + margin: 0 5px; + padding: 5px 10px; + border-radius: 4px; + border: 1px solid #2c4c61; + cursor: pointer; + color: #2c4c61; + &:hover { + background-color: #2c4c61; + color: #FFF; + } + &:first-child { + margin-left: 0; + } + &:last-child { + margin-right: 0; + } + &.disabled { + color: #777; + border-color: #777; + } + } +` + +const FilterRow = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + margin-bottom: 5px; + border: 1px solid #eaeaea; + padding: 10px; + border-radius: 4px; + p { + margin-right: 10px; + } + select { + padding: 5px 10px; + border-radius: 4px; + border: 1px solid #2c4c61; + cursor: pointer; + color: #2c4c61; + } +` + +const TitleMetric = styled.div` + display:flex; + justify-content: space-between; + color: #5c97bc; + padding: 5px 0px; + border-bottom: 2px solid #5c97bc; + width: 100%; + margin-top: 8px; + margin-bottom: 8px; + font-family: var(--bold); + font-weight: bold; + font-size: 1.6rem; +` + +class MetricsTags extends Component { + constructor (props) { + super(props) + this.state = { + yearList: [], + selectedYear: '', + isLoading: true, + documentsByTags: [], + documentsWithoutTags: [], + availableTags: [], + selectedTag: '', + showDocumentsTagName: '', + projects: [], + projectsByPage: [], + total: 0, + page: 1, + limit: 5, + totalPages: 0, + documentsWithoutTagsByPage: [], + totalDocumentsWithoutTags: 0, + pageDocumentsWithoutTags: 1, + limitDocumentsWithoutTags: 5, + totalPagesDocumentsWithoutTags: 0 + } + } + + componentDidMount () { + // complete a year list from 2017 to current year + const currentYear = new Date().getFullYear() + const yearList = [] + for (let i = 2019; i <= currentYear; i++) { + yearList.push(i) + } + this.setState({ yearList }) + this.init() + } + + init () { + const fetchArrays = [] + fetchArrays.push(fetch(`${API_URL}/api/v1/metric/documentByTags`, { + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + })) + fetchArrays.push(fetch(`${API_URL}/api/v1/document-tags/`, { + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + })) + Promise.all(fetchArrays) + .then((responses) => { + return Promise.all(responses.map((response) => { + return response.json() + })) + }) + .then((data) => { + const documentsByTags = data[0] + const availableTags = data[1].results + this.setState((prevState) => { + return { + documentsByTags: documentsByTags.tags, + documentsWithoutTags: documentsByTags.withoutTags.documents, + availableTags: availableTags, + isLoading: false, + documentsWithoutTagsByPage: documentsByTags.withoutTags.documents.slice(prevState.pageDocumentsWithoutTags - 1, prevState.limitDocumentsWithoutTags), + totalDocumentsWithoutTags: documentsByTags.withoutTags.documents.length, + totalPagesDocumentsWithoutTags: Math.ceil(documentsByTags.withoutTags.documents.length / prevState.limitDocumentsWithoutTags) + } + }) + }) + .catch((err) => console.error(err)) + } + + nextPage () { + this.setState((prevState) => { + return { + page: prevState.page + 1, + projectsByPage: prevState.projects.slice(prevState.page * prevState.limit, prevState.page * prevState.limit + prevState.limit) + } + }) + } + + prevPage () { + this.setState((prevState) => { + return { + page: prevState.page - 1, + projectsByPage: prevState.projects.slice((prevState.page - 2) * prevState.limit, (prevState.page - 2) * prevState.limit + prevState.limit) + } + }) + } + + nextPageDocumentsWithoutTags () { + this.setState((prevState) => { + return { + pageDocumentsWithoutTags: prevState.pageDocumentsWithoutTags + 1, + documentsWithoutTagsByPage: prevState.documentsWithoutTags.slice(prevState.pageDocumentsWithoutTags * prevState.limitDocumentsWithoutTags, prevState.pageDocumentsWithoutTags * prevState.limitDocumentsWithoutTags + prevState.limitDocumentsWithoutTags) + } + }) + } + + prevPageDocumentsWithoutTags () { + this.setState((prevState) => { + return { + pageDocumentsWithoutTags: prevState.pageDocumentsWithoutTags - 1, + documentsWithoutTagsByPage: prevState.documentsWithoutTags.slice((prevState.pageDocumentsWithoutTags - 2) * prevState.limitDocumentsWithoutTags, (prevState.pageDocumentsWithoutTags - 2) * prevState.limitDocumentsWithoutTags + prevState.limitDocumentsWithoutTags) + } + }) + } + + changeYear (e) { + const year = e.target.value + const tag = this.state.selectedTag + const accountableUser = this.state.selectedAccountableUser + this.setState({ isLoading: true }) + this.fetchByQuery(year, tag, accountableUser) + } + + changeTag (e) { + const year = this.state.selectedYear + const tag = e.target.value + const accountableUser = this.state.selectedAccountableUser + this.setState({ isLoading: true }) + this.fetchByQuery(year, tag, accountableUser) + } + + changeAccountableUser (e) { + const year = this.state.selectedYear + const tag = this.state.selectedTag + const accountableUser = e.target.value + this.setState({ isLoading: true }) + this.fetchByQuery(year, tag, accountableUser) + } + + isOpenOrClosed (closingDate) { + const today = new Date() + const closing = new Date(closingDate) + return today < closing + } + + listDocuments (tagName, documents) { + this.setState((prevState) => { + return { + showDocumentsTagName: tagName, + projects: documents, + projectsByPage: documents.slice(0, prevState.limit), + total: documents.length, + page: 1, + limit: prevState.limit, + totalPages: Math.ceil(documents.length / prevState.limit) + } + }) + } + + fetchByQuery (year, tag, accountableUser) { + let queries = [] + let query = '' + if (year) { + queries.push(`year=${year}`) + } + if (tag) { + queries.push(`tag=${tag}`) + } + if (queries.length > 0) { + query = `?${queries.join('&')}` + } + fetch(`${API_URL}/api/v1/metric/documentByTags${query}`, { + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + }) + .then((res) => res.json()) + .then((data) => { + this.setState((prevState) => { + return { + documentsByTags: data.tags, + documentsWithoutTags: data.withoutTags.documents, + isLoading: false, + documentsWithoutTagsByPage: data.withoutTags.documents.slice(prevState.pageDocumentsWithoutTags - 1, prevState.limitDocumentsWithoutTags), + totalDocumentsWithoutTags: data.withoutTags.documents.length, + totalPagesDocumentsWithoutTags: Math.ceil(data.withoutTags.documents.length / prevState.limitDocumentsWithoutTags), + selectedYear: year, + selectedTag: tag, + selectedAccountableUser: accountableUser, + showDocumentsTagName: '', + projects: [], + projectsByPage: [], + total: 0, + page: 1, + limit: prevState.limit, + totalPages: 0, + pageDocumentsWithoutTags: 1, + limitDocumentsWithoutTags: prevState.limitDocumentsWithoutTags, + } + }) + }) + .catch((err) => console.error(err)) + } + + render () { + const { documentsByTags, documentsWithoutTags, availableTags, isLoading, + showDocumentsTagName, projectsByPage, page, totalPages, documentsWithoutTagsByPage, + pageDocumentsWithoutTags, totalPagesDocumentsWithoutTags } = this.state + return ( + + +

Filtrar por año de creacion del proyecto

+ +
+ {/* +

Filtrar por etiqueta

+ +
*/} + { isLoading && ( + +

Cargando...

+
+ )} + { !isLoading && (
+ + + + Tag + Cantidad de proyectos + Listar proyectos + + + + { + documentsByTags.map((tagData) => { + return ( + + + + {tagData.name} + + + {tagData.documentsCount} + + this.listDocuments(tagData.name, tagData.documents)} /> + + + ) + }) + } + + + { + showDocumentsTagName && ( +
+ + Proyectos con etiqueta "{showDocumentsTagName}" + + + + + Proyecto + Autor + + + + { + projectsByPage.map((project) => { + return ( + + + + {project.title} + Versión {project.version} - {project.tags && project.tags.length > 0 ? project.tags.map((t) => t.name).join(', ') : '(Sin tags)'} + Fecha creacion: {new Date(project.createdAt).toLocaleDateString('es-ES', { hour: 'numeric', minute: 'numeric' })} - {this.isOpenOrClosed(project.closingDate) ? Abierto : Cerrado} - {this.published ? Visible : Oculto} + + + + + + + {project.author.fullname} + {project.author.email} + + + + + ) + }) + } + + + + + + + Página {page} de {totalPages} + + +
+ ) + } + { + documentsWithoutTags.length > 0 && ( +
+ + Proyectos sin etiquetas + + + + + Proyecto + Autor + + + + { + documentsWithoutTagsByPage.map((project) => { + return ( + + + + {project.title} + Versión {project.version} - {project.tags && project.tags.length > 0 ? project.tags.map((t) => t.name).join(', ') : '(Sin tags)'} + Fecha creacion: {new Date(project.createdAt).toLocaleDateString('es-ES', { hour: 'numeric', minute: 'numeric' })} - {this.isOpenOrClosed(project.closingDate) ? Abierto : Cerrado} + + + + + + + {project.author.fullname} + {project.author.email} + + + + + ) + }) + } + + + + + + + Página {pageDocumentsWithoutTags} de {totalPagesDocumentsWithoutTags} + + +
+ ) + } +
+ ) } + +
+ ) + } +} + +export default WithUserContext(MetricsTags) diff --git a/components/admin-metric/metricsUsers.js b/components/admin-metric/metricsUsers.js new file mode 100644 index 0000000..6559d89 --- /dev/null +++ b/components/admin-metric/metricsUsers.js @@ -0,0 +1,344 @@ +import React, { Component } from 'react' +import styled from 'styled-components' +import Link from 'next/link' +import getConfig from 'next/config' +import WithUserContext from '../with-user-context/component' + +const { publicRuntimeConfig: { API_URL } } = getConfig() + +const MetricWrapper = styled.div` + padding: 15px 0; +` + +const LoadingAnimation = styled.div` + width: 100%; + display: flex; + justify-content: center; + align-items: center; + background: #eaeaea; + padding: 20px 5px; + margin-bottom: 5px; + // animate text loading to fade in and out + p { + animation: fadeInOut 1.5s ease-in-out infinite; + } + @keyframes fadeInOut { + 0% { + opacity: 0; + } + 45% { + opacity: 1; + } + 100% { + opacity: 0; + } + } +` + +const UserWrapper = styled.div` + display: flex; + align-items: center; + flex-direction: row; +` +const UserAvatar = styled.div` + height: 32px; + width: 32px; + margin-right: 10px; + border-radius: 50%; + border: 1px solid #eaeaea; + background-image: url('${(props) => props.userId ? `${API_URL}/api/v1/users/${props.userId}/avatar` : '/static/assets/userdefault.png'}'); + background-size: cover; + background-position: center; +` +const UserData = styled.div` + +` +const UserName = styled.p` + font-weight: bold; + color: #5c97bc; +` + +const UserEmail = styled.p` + color: #777; + font-size: 1rem; +` + +const DataTable = styled.table` + width: 100%; + border-collapse: collapse; + border: 1px solid #eaeaea; + thead { + tr { + th { + text-align: left; + padding: 10px 5px; + border-bottom: 2px solid #eaeaea; + } + } + } + tbody { + tr { + td { + padding: 5px 5px + border-bottom: 1px solid #eaeaea; + } + } + } +` + +const ButtonsWrapper = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + margin-top: 20px; + button { + margin: 0 5px; + padding: 5px 10px; + border-radius: 4px; + border: 1px solid #2c4c61; + cursor: pointer; + color: #2c4c61; + &:hover { + background-color: #2c4c61; + color: #FFF; + } + &:first-child { + margin-left: 0; + } + &:last-child { + margin-right: 0; + } + &.disabled { + color: #777; + border-color: #777; + } + } +` + +const FilterRow = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + margin-bottom: 5px; + border: 1px solid #eaeaea; + padding: 10px; + border-radius: 4px; + p { + margin-right: 10px; + } + select { + padding: 5px 10px; + border-radius: 4px; + border: 1px solid #2c4c61; + cursor: pointer; + color: #2c4c61; + } + input { + padding: 5px 10px; + border-radius: 4px; + border: 1px solid #2c4c61; + cursor: pointer; + color: #2c4c61; + width: 100px; + text-align: center; + } +` + +const TitleMetric = styled.div` + display:flex; + justify-content: space-between; + color: #5c97bc; + padding: 5px 0px; + border-bottom: 2px solid #eaeaea; + width: 100%; + margin-bottom: 8px; + font-family: var(--bold); + font-weight: bold; + font-size: 1.6rem; +` + +class MetricsUsers extends Component { + constructor (props) { + super(props) + this.state = { + yearList: [], + selectedYear: '', + selectedMonths: '1', + monthOptions: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '16', '20', '24'], + isLoading: true, + totalUsers: 0, + totalCommonUsers: 0, + totalActiveUsers: 0 + } + } + + componentDidMount () { + // complete a year list from 2017 to current year + const currentYear = new Date().getFullYear() + const yearList = [] + for (let i = 2019; i <= currentYear; i++) { + yearList.push(i) + } + this.setState({ yearList }) + this.init() + } + + init () { + fetch(`${API_URL}/api/v1/metric/users?months=1`, { + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + }) + .then((res) => res.json()) + .then((data) => { + this.setState((prevState) => { + return { + totalUsers: data.totalUsers, + totalCommonUsers: data.totalCommonUsers, + totalActiveUsers: data.totalActiveUsers, + isLoading: false + } + }) + }) + .catch((err) => console.error(err)) + } + + changeYear (e) { + const year = e.target.value + const months = this.state.selectedMonths + this.setState({ isLoading: true }) + this.fetchByYear(year, months) + } + + changeMonths (e) { + const year = this.state.selectedYear + const months = e.target.value + console.log(e.target.value) + this.setState({ isLoading: true }) + this.fetchByYear(year, months) + } + + fetchByYear (year, months) { + let query = '' + let queries = [] + if (year) { + queries.push(`year=${year}`) + } + if (months) { + queries.push(`months=${months}`) + } + if (queries.length > 0) { + query = `?${queries.join('&')}` + } + fetch(`${API_URL}/api/v1/metric/users${query}`, { + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + }) + .then((res) => res.json()) + .then((data) => { + this.setState((prevState) => { + return { + totalUsers: data.totalUsers, + totalCommonUsers: data.totalCommonUsers, + totalActiveUsers: data.totalActiveUsers, + isLoading: false, + selectedYear: year, + selectedMonths: months + } + }) + }) + .catch((err) => console.error(err)) + } + + render () { + const { isLoading, totalUsers, totalCommonUsers, totalActiveUsers, monthOptions, yearList } = this.state + return ( + + + Datos generales + + +

Filtrar por año

+ +
+ { isLoading && ( + +

Cargando...

+
+ )} + { !isLoading && ( +
+ + + + Metrica + Valor + + + + + Cantidad total de usuarios + {totalUsers} + + + Cantidad usuarios comunes + {totalCommonUsers} + + + +
+ ) } +
+ + Usuarios activos + + + Usuarios activos en los últimos..  + + + { !isLoading && ( +
+ + + + Metrica + Valor + + + + + Usuarios activos (Iniciaron sesión) + {totalActiveUsers} + + + +
+ ) } +
+ ) + } +} + +export default WithUserContext(MetricsUsers) diff --git a/components/admin-metric/metricsUsersByRole.js b/components/admin-metric/metricsUsersByRole.js new file mode 100644 index 0000000..826e833 --- /dev/null +++ b/components/admin-metric/metricsUsersByRole.js @@ -0,0 +1,359 @@ +import React, { Component } from 'react' +import styled from 'styled-components' +import Link from 'next/link' +import getConfig from 'next/config' +import WithUserContext from '../with-user-context/component' + +const { publicRuntimeConfig: { API_URL } } = getConfig() + +const MetricWrapper = styled.div` + padding: 15px 0; +` + +const LoadingAnimation = styled.div` + width: 100%; + display: flex; + justify-content: center; + align-items: center; + background: #eaeaea; + padding: 20px 5px; + margin-bottom: 5px; + // animate text loading to fade in and out + p { + animation: fadeInOut 1.5s ease-in-out infinite; + } + @keyframes fadeInOut { + 0% { + opacity: 0; + } + 45% { + opacity: 1; + } + 100% { + opacity: 0; + } + } +` + +const UserWrapper = styled.div` + display: flex; + align-items: center; + flex-direction: row; +` +const UserAvatar = styled.div` + height: 32px; + width: 32px; + margin-right: 10px; + border-radius: 50%; + border: 1px solid #eaeaea; + background-image: url('${(props) => props.userId ? `${API_URL}/api/v1/users/${props.userId}/avatar` : '/static/assets/userdefault.png'}'); + background-size: cover; + background-position: center; +` +const UserData = styled.div` + +` +const UserName = styled.p` + font-weight: bold; + color: #5c97bc; +` + +const UserEmail = styled.p` + color: #777; + font-size: 1rem; +` + +const DataTable = styled.table` + width: 100%; + border-collapse: collapse; + border: 1px solid #eaeaea; + thead { + tr { + th { + text-align: left; + padding: 10px 5px; + border-bottom: 2px solid #eaeaea; + } + } + } + tbody { + tr { + td { + padding: 5px 5px + border-bottom: 1px solid #eaeaea; + } + } + } +` + +const ButtonsWrapper = styled.div` + display: flex; + justify-content: flex-start; + align-items: center; + margin-top: 20px; + button { + margin: 0 5px; + padding: 5px 10px; + border-radius: 4px; + border: 1px solid #2c4c61; + cursor: pointer; + color: #2c4c61; + &:hover { + background-color: #2c4c61; + color: #FFF; + } + &:first-child { + margin-left: 0; + } + &:last-child { + margin-right: 0; + } + &.disabled { + color: #777; + border-color: #777; + } + } +` + +// const FilterRow = styled.div` +// display: flex; +// justify-content: flex-start; +// align-items: center; +// margin-bottom: 5px; +// border: 1px solid #eaeaea; +// padding: 10px; +// border-radius: 4px; +// p { +// margin-right: 10px; +// } +// select { +// padding: 5px 10px; +// border-radius: 4px; +// border: 1px solid #2c4c61; +// cursor: pointer; +// color: #2c4c61; +// } +// ` + +const TitleMetric = styled.div` + display:flex; + justify-content: space-between; + color: #5c97bc; + padding: 5px 0px; + border-bottom: 2px solid #5c97bc; + width: 100%; + margin-top: 8px; + margin-bottom: 8px; + font-family: var(--bold); + font-weight: bold; + font-size: 1.6rem; +` + +class MetricsUsersByRole extends Component { + constructor (props) { + super(props) + this.state = { + + isLoading: true, + totalAdminUsers: 0, + totalAccountableUsers: 0, + adminUsers: [], + accountableUsers: [], + adminUsersByPage: [], + accountableUsersByPage: [], + adminTableTotal: 0, + adminTablePage: 1, + adminTableLimit: 10, + adminTableTotalPages: 0, + accountableTableTotal: 0, + accountableTablePage: 1, + accountableTableLimit: 10, + accountableTableTotalPages: 0 + } + } + + componentDidMount () { + this.init() + } + + init () { + fetch(`${API_URL}/api/v1/metric/usersByRole`, { + headers: { + Authorization: `Bearer ${this.props.authContext.keycloak.token}`, + 'Content-Type': 'application/json' + }, + method: 'GET' + }) + .then((res) => res.json()) + .then((data) => { + this.setState((prevState) => { + return { + isLoading: false, + adminUsers: data.adminUsers, + accountableUsers: data.accountableUsers, + adminUsersByPage: data.adminUsers.slice(prevState.adminTablePage - 1, prevState.adminTableLimit), + accountableUsersByPage: data.accountableUsers.slice(prevState.accountableTablePage - 1, prevState.accountableTableLimit), + adminTableTotal: data.adminUsers.length, + adminTablePage: 1, + adminTableLimit: prevState.adminTableLimit, + adminTableTotalPages: Math.ceil(data.adminUsers.length / prevState.adminTableLimit), + accountableTableTotal: data.accountableUsers.length, + accountableTablePage: 1, + accountableTableLimit: prevState.accountableTableLimit, + accountableTableTotalPages: Math.ceil(data.accountableUsers.length / prevState.accountableTableLimit) + } + }) + }) + .catch((err) => console.error(err)) + } + + nextPageAdmin () { + this.setState((prevState) => { + return { + adminTablePage: prevState.adminTablePage + 1, + adminUsersByPage: prevState.adminUsers.slice(prevState.adminTablePage * prevState.adminTableLimit, prevState.adminTablePage * prevState.adminTableLimit + prevState.adminTableLimit) + } + }) + } + + prevPageAdmin () { + this.setState((prevState) => { + return { + adminTablePage: prevState.adminTablePage - 1, + adminUsersByPage: prevState.adminUsers.slice((prevState.adminTablePage - 2) * prevState.adminTableLimit, (prevState.adminTablePage - 2) * prevState.adminTableLimit + prevState.adminTableLimit) + } + }) + } + + nextPageAccountable () { + this.setState((prevState) => { + return { + accountableTablePage: prevState.accountableTablePage + 1, + accountableUsersByPage: prevState.accountableUsers.slice(prevState.accountableTablePage * prevState.accountableTableLimit, prevState.accountableTablePage * prevState.accountableTableLimit + prevState.accountableTableLimit) + } + }) + } + + prevPageAccountable () { + this.setState((prevState) => { + return { + accountableTablePage: prevState.accountableTablePage - 1, + accountableUsersByPage: prevState.accountableUsers.slice((prevState.accountableTablePage - 2) * prevState.accountableTableLimit, (prevState.accountableTablePage - 2) * prevState.accountableTableLimit + prevState.accountableTableLimit) + } + }) + } + + render () { + const { isLoading, adminUsers, accountableUsers, adminUsersByPage, accountableUsersByPage, adminTablePage, adminTableTotalPages, accountableTablePage, accountableTableTotalPages } = this.state + + return ( + + + Lista de usuarios admin & autores + + { isLoading && ( + +

Cargando...

+
+ )} + { !isLoading && (
+ + + + Usuario (Rol admin) + Fecha creacion de cuenta + + + + { + adminUsersByPage.map((user) => { + return ( + + + + + + {user.fullname} + {user.email} + + + + {user.createdAt.split('T')[0]} + + ) + }) + } + + + + + + + Página {adminTablePage} de {adminTableTotalPages} + + +
+ + + + Usuario (Rol autor) + Fecha creacion de cuenta + + + + { + accountableUsersByPage.map((user) => { + return ( + + + + + + {user.fullname} + {user.email} + + + + {user.createdAt.split('T')[0]} + + ) + }) + } + + + + + + + Página {accountableTablePage} de {accountableTableTotalPages} + + +
+ ) } + +
+ ) + } +} + +export default WithUserContext(MetricsUsersByRole) diff --git a/containers/admin-section/component.js b/containers/admin-section/component.js index 5607b30..e871b4b 100644 --- a/containers/admin-section/component.js +++ b/containers/admin-section/component.js @@ -2,6 +2,7 @@ import React from 'react' import styled from 'styled-components' import PropTypes from 'prop-types' import TagsAdmin from '../../components/admin-tags/component' +import MetricAdmin from '../../components/admin-metric/component' import ProjectsAdmin from '../../components/admin-projects/component' import UsersAdmin from '../../components/admin-users/component' import UserEdit from '../../components/admin-edit-user/component' @@ -29,7 +30,9 @@ const AdminSection = (props) => { 'tags':, 'projects':, 'users':, - 'userEdit': + 'userEdit':, + 'metric': + } return( diff --git a/elements/navbar-usermenu/component.js b/elements/navbar-usermenu/component.js index b41f120..246ad2a 100644 --- a/elements/navbar-usermenu/component.js +++ b/elements/navbar-usermenu/component.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types' import styled from 'styled-components' import Icon from 'react-icons-kit' import { checkCircle } from 'react-icons-kit/fa/checkCircle' +// import { exclamationCircle } from 'react-icons-kit/fa/exclamationCircle' import getConfig from 'next/config' const { publicRuntimeConfig: { API_URL } } = getConfig() @@ -24,6 +25,7 @@ const Avatar = styled.div` background-image: url('${(props) => props.userId ? `${API_URL}/api/v1/users/${props.userId}/avatar?${props.updatedAt}` : '/static/assets/userdefault.png'}'); background-size: cover; background-position: center; + position: relative; @media (max-width: 760px) { width: 30px; min-width:30px; @@ -73,6 +75,15 @@ const Arrow = styled.i` display:none; } ` +// const IconProfileNeedsUpdate = styled.div` +// // warning color +// color: #f9a825; +// position: absolute; +// bottom: 0; +// right: 0; +// background-color: #fff; +// ` + const IconWrapper = styled.div` padding-left:.5rem; color: #5c97bc; @@ -83,7 +94,9 @@ const UserAvatar = ({ userId, name, party, badge, updatedAt }) => ( + updatedAt={updatedAt}> + {/* */} + {name} diff --git a/package-lock.json b/package-lock.json index 61c01f3..d9e81d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,12 @@ { "name": "leyesabiertas-web", - "version": "1.9.0", + "version": "2.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.9.0", + "name": "leyesabiertas-web", + "version": "2.1.0", "license": "ISC", "dependencies": { "@react-hook/media-query": "^1.1.1", @@ -2615,7 +2616,6 @@ "anymatch": "^2.0.0", "async-each": "^1.0.1", "braces": "^2.3.2", - "fsevents": "^1.2.7", "glob-parent": "^3.1.0", "inherits": "^2.0.3", "is-binary-path": "^1.0.0", @@ -10523,7 +10523,6 @@ "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", "dependencies": { "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", "yargs": "~3.10.0" }, "bin": { diff --git a/package.json b/package.json index a735a9f..1701528 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leyesabiertas-web", - "version": "1.9.0", + "version": "2.1.0", "description": "", "main": "index.js", "scripts": {