Skip to content

Commit

Permalink
Adding scores export and import
Browse files Browse the repository at this point in the history
  • Loading branch information
Hartorn committed Dec 11, 2018
1 parent d9f83aa commit 446c43f
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 26 deletions.
2 changes: 2 additions & 0 deletions app/i18n/fr-fr.js
Expand Up @@ -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",
Expand Down
24 changes: 24 additions & 0 deletions 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;
26 changes: 2 additions & 24 deletions app/views/events/detail/index.js
Expand Up @@ -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';
Expand All @@ -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']
Expand Down Expand Up @@ -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');
}
Expand Down
50 changes: 48 additions & 2 deletions app/views/events/detail/round-list-view.js
Expand Up @@ -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,
Expand All @@ -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],
Expand All @@ -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));
},
Expand Down Expand Up @@ -147,17 +154,56 @@ 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 (
<div data-app='round-list-page' >
{this.props.noLive && <h4 className='website-title'>{translate('label.rounds')}</h4>}
< div className='pad-bottom' >
<div className='pad-buttons' >
<Button label='label.goToResults' onClick={() => { navigate(`event/${this.props.id}/results`) }} />
<Button label='label.goToResults' onClick={() => { navigate(`event / ${this.props.id} / results`) }} />
{this.props.id && this.state.roundId && <Button label='label.refreshResult' onClick={() => { actions.getRoundScore({ id: this.props.id, idRound: this.state.roundId }); }} />}
</div>
{isAdmin() && <div><Button label='label.addRound' onClick={this.addRound} /></div>}
{isModo() && <div>
<Button label={'label.exportScore'} onClick={this.exportEmptyScoreFile} />
</div>}

{isModo() && <div>
<input type='file' ref={this.fileInput} hidden accept='text/csv' onChange={this.importResults} />
<Button label={'label.importScore'} onClick={() => this.fileInput.current.click()} />
</div>}

{this.state.roundId && this.renderRound()}
<div className='pad-buttons' style={{ display: 'flex' }} >
{isModo() && this.state.roundId && this.state.roundId !== 'ALL' &&
Expand Down

0 comments on commit 446c43f

Please sign in to comment.