From 446c43f58403afa87bf7be3c796e2384fcdef9b3 Mon Sep 17 00:00:00 2001 From: Hartorn Date: Tue, 11 Dec 2018 22:44:12 +0100 Subject: [PATCH] Adding scores export and import --- app/i18n/fr-fr.js | 2 + app/utilities/download-data.js | 24 +++++++++++ app/views/events/detail/index.js | 26 +---------- app/views/events/detail/round-list-view.js | 50 +++++++++++++++++++++- 4 files changed, 76 insertions(+), 26 deletions(-) create mode 100644 app/utilities/download-data.js diff --git a/app/i18n/fr-fr.js b/app/i18n/fr-fr.js index 6fb0577..aea46f5 100644 --- a/app/i18n/fr-fr.js +++ b/app/i18n/fr-fr.js @@ -56,6 +56,8 @@ export default { home: 'Fight for Sub' }, label: { + exportScore: "Exporter les scores en CSV", + importScore: "Importer les scores en CSV", exportValidated: "Exporter au format CSV", validated: "Validés", waitingValidation: "En attente de validation", diff --git a/app/utilities/download-data.js b/app/utilities/download-data.js new file mode 100644 index 0000000..c88d53c --- /dev/null +++ b/app/utilities/download-data.js @@ -0,0 +1,24 @@ +/* Credits to https://github.com/kennethjiang/react-file-download */ +const downloadData = (data, filename, typeMime) => { + const blob = new Blob([data], { + type: typeMime + }); + if (typeof window.navigator.msSaveBlob !== 'undefined') { + // IE workaround for "HTML7007: One or more blob URLs were + // revoked by closing the blob for which they were created. + // These URLs will no longer resolve as the data backing + // the URL has been freed." + window.navigator.msSaveBlob(blob, filename); + } else { + const csvURL = window.URL.createObjectURL(blob); + const tempLink = document.createElement('a'); + tempLink.href = csvURL; + tempLink.setAttribute('download', filename); + tempLink.setAttribute('target', '_blank'); + document.body.appendChild(tempLink); + tempLink.click(); + document.body.removeChild(tempLink); + } +}; + +export default downloadData; \ No newline at end of file diff --git a/app/views/events/detail/index.js b/app/views/events/detail/index.js index 3377fea..934f88e 100644 --- a/app/views/events/detail/index.js +++ b/app/views/events/detail/index.js @@ -13,6 +13,7 @@ import AddPopin from '@/views/events/add-popin'; import UserLine from '@/components/user-line'; import List from '@/components/list'; import { navigate } from '@/utilities/router'; +import downloadData from '@/utilities/download-data'; import { isAdmin, isModo } from '@/utilities/check-rights'; import EventStore from '@/stores/event'; import eventActions from '@/action/event'; @@ -21,29 +22,6 @@ import UserPopin from './detail-user'; import RecapEvent from './recap-event'; import RoundListView from './round-list-view'; -/* Credits to https://github.com/kennethjiang/react-file-download */ -const downloadData = (data, filename, typeMime) => { - const blob = new Blob([data], { - type: typeMime - }); - if (typeof window.navigator.msSaveBlob !== 'undefined') { - // IE workaround for "HTML7007: One or more blob URLs were - // revoked by closing the blob for which they were created. - // These URLs will no longer resolve as the data backing - // the URL has been freed." - window.navigator.msSaveBlob(blob, filename); - } else { - const csvURL = window.URL.createObjectURL(blob); - const tempLink = document.createElement('a'); - tempLink.href = csvURL; - tempLink.setAttribute('download', filename); - tempLink.setAttribute('target', '_blank'); - document.body.appendChild(tempLink); - tempLink.click(); - document.body.removeChild(tempLink); - } -}; - @connectToStore([{ store: EventStore, properties: ['eventUserList', 'eventDetail', 'eventUserRegistration'] @@ -90,7 +68,7 @@ class DetailEventView extends React.Component { .map(({ username, views, followers, url }) => `${username};${views};${followers};${url}` ); - const fileName = `${this.props.event.name}.csv`.replace(/ /g, '_'); + const fileName = `${this.props.event.name}_validated.csv`.replace(/ /g, '_'); downloadData(header.concat(data).join('\n'), fileName, 'text/csv'); } diff --git a/app/views/events/detail/round-list-view.js b/app/views/events/detail/round-list-view.js index f3310e5..4e35b51 100644 --- a/app/views/events/detail/round-list-view.js +++ b/app/views/events/detail/round-list-view.js @@ -21,6 +21,7 @@ import EventStore from '@/stores/event'; import actions from '@/action/event'; import FFSWebSocket from '@/utilities/web-socket'; import TwitchLive from './twitch-live'; +import downloadData from '@/utilities/download-data'; export default connectToStore([{ store: EventStore, @@ -29,7 +30,12 @@ export default connectToStore([{ { store: UserStore, properties: ['profile'] -}], () => ({ eventRoundList: EventStore.getEventRoundList(), eventRoundDetail: EventStore.getEventRoundDetail(), userList: EventStore.getEventUserList() || [] })) +}], () => ({ + eventRoundList: EventStore.getEventRoundList(), + eventRoundDetail: EventStore.getEventRoundDetail(), + userList: EventStore.getEventUserList() || [], + event: EventStore.getEventDetail() || {} +})) (createReactClass({ displayName: 'RoundListView', mixins: [formPreset], @@ -45,6 +51,7 @@ export default connectToStore([{ if (this.props.eventRoundList && this.props.eventRoundList.length > 0) { this.onChangeRound(this.props.eventRoundList[0]); } + this.fileInput = React.createRef(); this.eventWs = new FFSWebSocket(this.props.id, (data, topics) => this.onWsUpdate(data)); }, @@ -147,6 +154,36 @@ export default connectToStore([{ } } }, + importResults(event) { + event.preventDefault(); + const files = Array.prototype.map.call(event.target.files || event.dataTransfer.files, (elt) => elt); + const file = files.length >= 1 ? files[0] : null; + const reader = new FileReader(); + + reader.onloadend = () => { + const text = reader.result; + text.replace(/\r/g, '').split('\n').map(elt => elt.split(';')) + .forEach(([userId, , score], idx) => { + // Skipping header + if (idx === 0) { + return; + } + + actions.updateUserScore({ id: this.props.id, idRound: this.state.roundId, idUser: userId, score }, this) + }); + }; + reader.readAsText(file); + }, + exportEmptyScoreFile() { + const header = ['TwitchId;Pseudo;Score']; + const data = (this.props.userList || []) + .filter(({ status }) => status === 'VALIDATED') + .sort((a, b) => a.username.localeCompare(b.username)) + .map(({ username, id, twitchId }) => `${id || twitchId};${username};${(this.props.eventRoundDetail.filter(({ id: scoreId }) => scoreId === (id || twitchId))[0] || {}).score || 0}`); + const fileName = `${this.props.event.name}_score.csv`.replace(/ /g, '_'); + + downloadData(header.concat(data).join('\n'), fileName, 'text/csv'); + }, /** @inheritDoc */ renderContent() { return ( @@ -154,10 +191,19 @@ export default connectToStore([{ {this.props.noLive &&

{translate('label.rounds')}

} < div className='pad-bottom' >
-
{isAdmin() &&
} + {isModo() &&
+
} + + {isModo() &&
+ +
} + {this.state.roundId && this.renderRound()}
{isModo() && this.state.roundId && this.state.roundId !== 'ALL' &&