diff --git a/package.json b/package.json index 95854cff..6e8b3ce6 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@types/react-csv": "^1.1.1", "apollo-cache-inmemory": "^1.0.0", "apollo-client": "^2.0.1", "apollo-client-preset": "^1.0.1", diff --git a/src/actions/statisticsActions.js b/src/actions/statisticsActions.js index abc79207..42b3bab5 100644 --- a/src/actions/statisticsActions.js +++ b/src/actions/statisticsActions.js @@ -2,7 +2,10 @@ import { queryStatistics } from '../api/graphQL' import { FETCH_STATISTICS_START, FETCH_STATISTICS_PASS, - FETCH_STATISTICS_FAIL + FETCH_STATISTICS_FAIL, + FETCH_PARTNER_STATISTICS_PASS, + FETCH_PARTNER_STATISTICS_START, + FETCH_PARTNER_STATISTICS_FAIL } from '../types' function startFetchStatistics () { @@ -33,7 +36,35 @@ export function fetchStatisticsPass (statistics) { ) } -export default function fetchStatistics (semester, partner) { +function startFetchPartnerStatistics () { + return ( + { + type: FETCH_PARTNER_STATISTICS_START + } + ) +} + +function fetchPartnerStatisticsFail (error) { + return ( + { + type: FETCH_PARTNER_STATISTICS_FAIL, + payload: { error } + } + ) +} + +export function fetchPartnerStatisticsPass (partnerStatistics) { + return ( + { + type: FETCH_PARTNER_STATISTICS_PASS, + payload: { + partnerStatistics + } + } + ) +} + +export function fetchStatistics (semester, partner) { return function disp (dispatch) { dispatch(startFetchStatistics()) queryStatistics(semester, partner) @@ -43,4 +74,16 @@ export default function fetchStatistics (semester, partner) { dispatch(fetchStatisticsFail(e.message)) }) } +} + +export function fetchPartnerStatistics (semester, partner) { + return function disp (dispatch) { + dispatch(startFetchPartnerStatistics()) + queryStatistics(semester, partner) + .then(res => { dispatch(fetchPartnerStatisticsPass(res)) } + ) + .catch((e) => { + dispatch(fetchPartnerStatisticsFail(e.message)) + }) + } } \ No newline at end of file diff --git a/src/api/graphQL.js b/src/api/graphQL.js index f5e03c2a..7849e87f 100644 --- a/src/api/graphQL.js +++ b/src/api/graphQL.js @@ -94,6 +94,7 @@ function requestedTime(requirements, semester){ }) return reqTime } +const removeRejectedProposals = proposals => proposals.filter(proposal => proposal.status !== 'Rejected') export function convertProposals(proposals, semester, partner){ if (!proposals.proposals){ return []} @@ -264,7 +265,10 @@ export function queryProposals(semester, partner){ ` return graphqlClient().post('/graphql', { query }) .then( - response => convertProposals(response.data.data, semester, partner) + response => { + const convertedProposals = convertProposals(response.data.data, semester, partner) + return removeRejectedProposals(convertedProposals) + } ) } diff --git a/src/components/Filters.js b/src/components/Filters.js index 94c06a82..0535de50 100644 --- a/src/components/Filters.js +++ b/src/components/Filters.js @@ -10,7 +10,7 @@ import { storePartnerAllocations } from '../actions/timeAllocationActions' import { semestersArray, getPartnerList, getAstronomersList } from '../util/filters' import { defaultSemester } from '../util' import { ADMINISTRATOR, SALT_ASTRONOMER, BOARD, TAC_CHAIR } from '../types' -import fetchStatistics from '../actions/statisticsActions' +import {fetchStatistics, fetchPartnerStatistics} from '../actions/statisticsActions' class Filters extends React.Component { updateSemester = value => { @@ -18,7 +18,7 @@ class Filters extends React.Component { dispatch(fetchProposals( value, filters.selectedPartner)) dispatch(fetchPartnerStatProposals( value, filters.selectedPartner)) dispatch(fetchStatistics( value, filters.selectedPartner)) - dispatch(fetchStatistics(value, filters.selectedPartner)) + dispatch(fetchPartnerStatistics(value, filters.selectedPartner)) dispatch(storePartnerAllocations(value, filters.selectedPartner)) dispatch(semesterChange(value)) }; @@ -26,7 +26,8 @@ class Filters extends React.Component { const { dispatch, filters } = this.props dispatch(fetchProposals( filters.selectedSemester, value)) dispatch(fetchPartnerStatProposals( filters.selectedPartnerStatsSemester, value)) - dispatch(fetchStatistics(filters.selectedPartnerStatsSemester, value)) + dispatch(fetchStatistics(filters.selectedSemester, value)) + dispatch(fetchPartnerStatistics(filters.selectedPartnerStatsSemester, value)) dispatch(storePartnerAllocations(filters.selectedSemester, value)) dispatch(partnerChange(value)) }; diff --git a/src/components/messages/NoRejectedProposalsMessage.js b/src/components/messages/NoRejectedProposalsMessage.js new file mode 100644 index 00000000..703c5355 --- /dev/null +++ b/src/components/messages/NoRejectedProposalsMessage.js @@ -0,0 +1,13 @@ +import React from 'react' + +const NoRejectedProposalsMessage = () => ( +
+
+
+

Please note that rejected proposals are not included.

+
+
+
+) + +export default NoRejectedProposalsMessage \ No newline at end of file diff --git a/src/components/pages/LiaisonPage.js b/src/components/pages/LiaisonPage.js index 579d8da0..51dca871 100644 --- a/src/components/pages/LiaisonPage.js +++ b/src/components/pages/LiaisonPage.js @@ -5,6 +5,7 @@ import {downloadSummary, getLiaisonUsername} from '../../util' import {reduceProposalsPerAstronomer} from '../../util/filters' import {ADMINISTRATOR} from '../../types' import {isLiaisonAstronomerUpdated} from '../../util/proposal-filtering' +import NoRejectedProposalsMessage from '../messages/NoRejectedProposalsMessage' const requestSummary = (event, proposalCode, semester) => { @@ -28,6 +29,7 @@ const LiaisonPage = ({proposals, filters, astronomers, user, setLiaison, initPro const { username } = user return (
+ p.status !== 'DELETED' && p.status !== 'REJECTED') return (
+

Click here for navigating to the dashboard

@@ -149,7 +151,7 @@ export default connect(store => ( user: store.user.user, partnerShareTimes: store.partnerShareTimes.partnerShareTimes, loading: store.partnerStatProposals.fetching, - statistics: store.statistics.statistics, + statistics: store.statistics.partnerStatistics, submittingCompletionComment: store.partnerStatProposals.submittingCompletionComment, submittedCompletionComment: store.partnerStatProposals.submittedCompletionComment, submittingCommentError: store.partnerStatProposals.errors.submittingCommentError diff --git a/src/components/pages/StatisticsPage.js b/src/components/pages/StatisticsPage.js index 65552851..9c5e8b8a 100644 --- a/src/components/pages/StatisticsPage.js +++ b/src/components/pages/StatisticsPage.js @@ -25,6 +25,7 @@ import RSSDetectorModeTable from '../tables/statisticsTables/RSSDetectorModeTabl import HRSStatistics from '../tables/statisticsTables/HRSStatistics' import RSSObservingModeTable from '../tables/statisticsTables/RSSObservingModeTable' import SALTICAMStatistics from '../tables/statisticsTables/SALTICAMStatistics' +import NoRejectedProposalsMessage from '../messages/NoRejectedProposalsMessage' class StatisticsPage extends React.Component { @@ -53,7 +54,7 @@ class StatisticsPage extends React.Component { return(
- +
diff --git a/src/components/pages/TechReviewPage.js b/src/components/pages/TechReviewPage.js index bc42d72e..0ef86e75 100644 --- a/src/components/pages/TechReviewPage.js +++ b/src/components/pages/TechReviewPage.js @@ -9,6 +9,7 @@ import { import TechReviewTable from '../tables/TechReviewTable' import {defaultSemester, getLiaisonUsername} from '../../util' import { reduceProposalsPerAstronomer } from '../../util/filters' +import NoRejectedProposalsMessage from '../messages/NoRejectedProposalsMessage' class TechReviewPage extends React.Component { @@ -47,8 +48,8 @@ class TechReviewPage extends React.Component { } return( -
+ +
downloadSummaries(partnerProposals[ partner ] || [], semester, partner) }> Download summary files + { // eslint-disable-next-line + submittedTimeAllocations.partner !== partner ?
+ : submittedTimeAllocations.results ?
Successfully Submitted
+ :
Fail to submit time allocations
+ } { canSubmitForPartner &&
- { // eslint-disable-next-line - submittedTimeAllocations.partner !== partner ?
- : submittedTimeAllocations.results ?
Successfully Submitted
- :
Fail to submit time allocations
- }
diff --git a/src/components/tables/ProposalsPerPartner.js b/src/components/tables/ProposalsPerPartner.js index f0bdd7ab..05083482 100644 --- a/src/components/tables/ProposalsPerPartner.js +++ b/src/components/tables/ProposalsPerPartner.js @@ -4,7 +4,7 @@ import _ from 'lodash' import { illegalAllocation } from '../../util/allocation' import { goodTime, badTime } from '../../types' import { getTechnicalReport } from '../../util/technicalReports' -import {downloadSummary} from '../../util/index' +import { downloadSummary, NumberParser } from '../../util/index' const TimeAllocationInput = ({onChange, proposal, priority, partner, name}) => { const sty = illegalAllocation(proposal, priority, partner) ? badTime : goodTime @@ -164,10 +164,14 @@ const ProposalsPerPartner = ({proposals, partner, tacCommentChange, allocatedTim }
{ - parseFloat(p.allocatedTime[ partner ] ? p.allocatedTime[ partner ].p0 : 0 ) + - parseFloat(p.allocatedTime[ partner ] ? p.allocatedTime[ partner ].p1 : 0 ) + - parseFloat(p.allocatedTime[ partner ] ? p.allocatedTime[ partner ].p2 : 0 ) + - parseFloat(p.allocatedTime[ partner ] ? p.allocatedTime[ partner ].p3 : 0 ) + new NumberParser(window.navigator.language) + .parse((p.allocatedTime[ partner ] ? p.allocatedTime[ partner ].p0 : 0 )) + + new NumberParser(window.navigator.language) + .parse((p.allocatedTime[ partner ] ? p.allocatedTime[ partner ].p1 : 0 )) + + new NumberParser(window.navigator.language) + .parse((p.allocatedTime[ partner ] ? p.allocatedTime[ partner ].p2 : 0 )) + + new NumberParser(window.navigator.language) + .parse((p.allocatedTime[ partner ] ? p.allocatedTime[ partner ].p3 : 0 )) }
{ canAllocate ? diff --git a/src/reducers/statistics.js b/src/reducers/statistics.js index fe39ab5e..6d914e20 100644 --- a/src/reducers/statistics.js +++ b/src/reducers/statistics.js @@ -1,7 +1,10 @@ import { FETCH_STATISTICS_START, FETCH_STATISTICS_PASS, - FETCH_STATISTICS_FAIL + FETCH_STATISTICS_FAIL, + FETCH_PARTNER_STATISTICS_START, + FETCH_PARTNER_STATISTICS_PASS, + FETCH_PARTNER_STATISTICS_FAIL } from '../types' const initialState = { @@ -46,6 +49,37 @@ export default function statistics (state = initialState, action = {}) { statistics: action.payload.statistics } } + case FETCH_PARTNER_STATISTICS_START: { + return { + ...state, + fetching: true, + fetched: false + } + } + case FETCH_PARTNER_STATISTICS_FAIL: { + return { + ...state, + fetching: false, + fetched: false, + partnerStatistics: {}, + errors: { + ...state.errors, + fetchingError: action.payload.error + } + } + } + case FETCH_PARTNER_STATISTICS_PASS: { + return { + ...state, + fetching: false, + fetched: true, + errors: { + ...state.errors, + fetchingError: null + }, + partnerStatistics: action.payload.partnerStatistics + } + } default: { return state } diff --git a/src/styles/messages/messages.css b/src/styles/messages/messages.css index 1db554a8..3887dbfc 100644 --- a/src/styles/messages/messages.css +++ b/src/styles/messages/messages.css @@ -35,3 +35,11 @@ color: #013d19; padding: 2em 0 1em 0; } + +.notification { + text-align: center; + background: #cccccc; + font-size: 20px; + padding: 0.1em; + font-weight: bold; +} diff --git a/src/util/allocation.js b/src/util/allocation.js index 6feb113a..6d5a25cb 100644 --- a/src/util/allocation.js +++ b/src/util/allocation.js @@ -1,8 +1,8 @@ -import { isFloat } from '../util' +import { isFloat, NumberParser } from '../util' export function illegalAllocation(proposal, priority, partner) { const t = proposal.allocatedTime[ partner ] ? proposal.allocatedTime[ partner ][ priority ] : 0 - return !isFloat(t) || parseFloat(t) < 0 + return !isFloat(t) || new NumberParser(window.navigator.language).parse(t) < 0 } export function checkAllocatedTimes(proposals, partner){ @@ -35,7 +35,8 @@ export function getQuaryToAddAllocation(proposals, partner, semester){ `{ proposalCode: "${ p.proposalCode }", priority: ${ t }, - time: ${ p.allocatedTime[ partner ] ? p.allocatedTime[ partner ][ priority ] : 0 } + time: ${ p.allocatedTime[ partner ] ? + new NumberParser().parse(p.allocatedTime[ partner ][ priority ]) : 0 } }` ) }) diff --git a/src/util/index.js b/src/util/index.js index 7f6e29d4..f6cd4756 100644 --- a/src/util/index.js +++ b/src/util/index.js @@ -2,6 +2,26 @@ import { saveAs } from 'file-saver' import * as types from '../types' import { jsonClient } from '../api' +export class NumberParser { + constructor(locale) { + const parts = new Intl.NumberFormat(locale).formatToParts(12345.6) + const numerals = [...new Intl.NumberFormat(locale, {useGrouping: false}).format(9876543210)].reverse() + const index = new Map(numerals.map((d, i) => [d, i])) + this._group = new RegExp(`[${ parts.find(d => d.type === 'group').value }]`, 'g') + this._decimal = new RegExp(`[${ parts.find(d => d.type === 'decimal').value }]`) + this._numeral = new RegExp(`[${ numerals.join('') }]`, 'g') + this._index = d => index.get(d) + } + parse(string) { + const str = string.toString().trim() + .replace(this._group, '') + .replace(' ', '') + .replace(this._decimal, '.') + .replace(this._numeral, this._index) + return str ? +str : NaN + } +} + /** * Return the sum of a and b * */ @@ -177,12 +197,10 @@ export function canDo(user, action, partner) { * @return boolean (true)if value can be a float * */ export function isFloat(val) { - const floatRegex = /^[+-]?\d+(?:[.,]\d*?)?$/ - if (!floatRegex.test(val)) - return false if (Array.isArray(val))return false - const temp = parseFloat(val) + // Check if the number have a correct notation + const temp = new NumberParser(window.navigator.language).parse(val) return !isNaN(temp) } @@ -249,7 +267,9 @@ export function allocatedTimeTotals( proposals, partner ){ proposals.forEach(p => { [0, 1, 2, 3, 4].forEach( pr => { if(p.allocatedTime && p.allocatedTime[ partner ]){ - total[ `p${ pr }` ] += parseFloat(p.allocatedTime[ partner ] ? p.allocatedTime[ partner ][ `p${ pr }` ] : 0) || 0 + total[ `p${ pr }` ] += new NumberParser(window.navigator.language) + .parse(p.allocatedTime[ partner ] ? + (p.allocatedTime[ partner ][ `p${ pr }` ]) : '0') || 0 } }) })